

             /*** Copyright  1988 by Joseph M. Hinkle ***/

	This is raw text prepared for articles and a book on programming the
Amiga for novices with the intent of quickly getting the reader into the
more powerful features fairly quickly.  There are some errors of
explanation of more abstruse things which I need to clean up, but in the
main() (Ha! a little c joke) you will find this useful.  Because it is a
kind of super-outline of the book it moves along at a pace you may find
breathtaking if you are new to programming the machine, but careful study
of this and the ROM Kernel Manuals will help you greatly.
	The tutorial explains lists, tasks, and startup code with comments on
programming style and example programs which I have tested thoroughly.  The
discussion and examples are valid for Workbench v1.2 (v1.3 is just becoming
available as I post this, so I haven't seen anything on it yet, but I doubt
there would be much trouble using that version).  I use the Lattice v4.01
compiler and don't have Manx, so can't say much about compiling with that
one.  Expect some minor trouble, but the examples are Amiga specific, not
compiler specific, for the most part.
	I hang around A-Link in Everett, Washington at (206) 774-4735, run by
the charming and gracious John (He paid me to say this) Willott, and I go
by the name Marty Hinkle on the board.  Please send me questions and
comments there.
	I also have a tutorial on making shared libraries yourself without using
assembly code (other than the skeleton in the RKM) which does not require
you to be proficient in assembly coding.  If someone is interested in that,
I'll straighten it up and upload it.
   This production comes from extensive study of the machine itself with
little input from Commodore (It is probably quite shortsighted of me to not
be in their developer's program).  A shareware style donation will not be
inappropriate, and you will get a free copy of the whole book if I can ever
get a publisher.

                                                  Joseph M. Hinkle
                                                  Route 2, Box 2647
                                                  Lopez, Washington 98261

                                                  Posted September 20, 1988

/*************************************************************************/

Programming the Amiga
From Novice to Advanced

	Anyone getting the idea to write a useful program on the Amiga decides
to get the Rom Kernel Manual and DOS manual to learn how the system works.
The size of the manuals gives one pause.  Reading them is daunting.  After
a bit of study one realizes there is no good overview of the executive and
disk operating system, but there is plenty of detail.  Trying out some of
the example programs can lead to odd results as there are many small
mistakes in them which one can overlook.  After getting something to work
many questions are left, such as "What will make this program work under
Workbench?", "How can this program be made to multitask?", "What is a
process?", and "How can this program be loaded by another?".  All of these
are mentioned in the books and there is much discussion of them, but
nowhere are there clear examples of useful programs that implement these
things.  You will find, after some experience with the machine, that nearly
every point is mentioned somewhere in the books.  Tying them all together
is the difficult part.
	I will cover these questions and more by showing a simple program to
establish a style, then using that program to build ever more involved
programs which will take you through the heart of the Amiga.  I will refer
to the RKM (ROM Kernel Manual, Addison-Wesley, in four volumes: ROM Kernel
Reference Manual: Libraries and Devices,  ROM Kernel Reference
Manual:Exec, Intuition Reference Manual, and Hardware Reference Manual),
and the AmigaDOS Manual, Bantam Books.  These might appear pricey, but for
serious programming on the Amiga they are indispensable.  If you are new
to the c programming language you should also have a text on c such as the
original language definition "The c Programming Language" by Kernighan and
Ritchie, Prentice-Hall, or another textbook on the subject.  Make sure you
understand data structures because there will be a lot of references to
them.  Let's get started:
	We will need a timer for this experiment, something that goes tick,
tick, tick, every second or so.  There is a section in the RKM: Libraries
and Devices called Timer Device. To see if everything will work as
advertised, let's try a simple delay.  We need to open a timer, run it,
and then close it.  In order to open a timer we need to fill in a
timerequest structure (RKM:Libraries and Devices, Include Files,
devices/timers.h.  Note the .i structures are for assembly language
writers).  Note the structure is tagged timerequest, not timeRequest as in
the text.  The structure is:

struct timerequest {
	struct IORequest tr_node;
	struct timeval	tr_time;
}

Aha! A typical Amiga data structure!  It consists of nothing more than
other structures!  Sigh.  There is hope, though.  The struct timeval is in
the same include file:

struct timeval {
	ULONG tv_secs;
	ULONG tv_micro;
}

But what is a ULONG?  Look in RKM:Exec, Include Files, exec/types.h:

typedef unsigned long ULONG;

Now we know the timerequest structure is a template for an IORequest
structure and two 32 bit quantities.  At the top of the file timer.h there
is a line:

#include exec/io.h

So, while you're thumbing through RKM:Exec, look at exec/io.h.  You will
find:

struct IORequest {
	struct Message	io_Message;
	struct Device	*io_Device;
	struct Unit		*io_Unit;
	UWORD	io_Command;
	UBYTE	io_Flags;
	BYTE	io_Error;
}

That file #includes exec/ports.h, which defines the struct Message and
struct MsgPort.  That file in turn references exec/nodes.h, exec/lists.h
and exec/tasks.h, which contain the definitions needed to completely
specify the struct timerequest (In my compiler's include files there is
no mention of #including exec/types.h, so I have to do that myself.
#including devices/timer.h gets all the rest).  The reason for belaboring
all the various data structures is to show you what happens when a request
is made, that is, just what data is passed around the machine and what
Exec does with it.  Exec, the executive program, supervises the use of the
microprocessor, responding to interrupts and checking tables of things to
do.  It is generally invisible to you but it helps to know it is there,
and we will be using its library of functions often.
	This exercise also gets you used to referencing the RKM, which you will
also be doing often.  Continuing with the full definition of struct
timerequest,  we see it is a template for memory arranged like this,
assuming we call the structure TR:

LONG TR.tr_node.io_Message.mn_Node.ln_Succ ; points to the following node
LONG TR.tr_node.io_Message.mn_Node.ln_Pred ; points to the preceding node
BYTE TR.tr_node.io_Message.mn_Node.ln_Type ; defines the type of node
BYTE TR.tr_node.io_Message.mn_Node.ln_Pri  ; this node's priority
LONG TR.tr_node.io_Message.mn_Node.ln_Name ; points to a text string name
LONG TR.tr_node.io_Message.mn_ReplyPort    ; points to a message port
WORD TR.tr_node.io_Message.mn_Length       ; length of this whole block
LONG TR.tr_node.io_Device                  ; points to a struct Device
LONG TR.tr_node.io_Unit                    ; points to a struct Unit
WORD TR.tr_node.io_Command                 ; a command
BYTE TR.tr_node.io_Flags                   ; flags set
BYTE TR.tr_node.io_Error                   ; error returned
LONG TR.tr_time.tv_secs                    ; seconds requested
LONG TR.tr_time.tv_micro                   ; microseconds requested

	These 40 bytes are the complete timerequest structure.  We fill in some
members ourselves and functions provided in the system fill in others.  In
particular, we need to fill in what kind of timer it is, how we want the
timer to behave (the command), and when we want the timer to reply to us.
	First, however, we have to allocate some memory for the structure.
There is a routine available which will do that for us, CreateExtIO(), but
one of the things it needs is the address of a message port (struct
MsgPort), which, as you recall from looking in exec/ports.h, is a little
structure itself, starting with a Node structure.  Are you getting the
idea that everything starts with a Node structure?  Just about everything
does, because Exec uses these to link them into Lists (RKM:Exec, Lists)
which are scanned periodically to see if anything needs doing, or when a
routine wants to see if it has any mail.  Whole data structures are not
passed around in the machine, just addresses of nodes, which also happen
to be the first addresses of most structures.  We will get into detail on
that later; suffice to say that if we ever get this memory allocated we
will ask somebody to link our structure into some list or another so it
will get processed.
	There is a good discussion of memory allocation in RKM:Exec, Memory
Allocation but fortunately there is a routine for that, too, for certain
cases such as we need now.  CreatePort(), which needs only a name and a
priority (RKM:Exec, Tasks, et al), will provide us with the address of a
MsgPort structure we can use to call CreateExtIO(), which will provide us
with an address of a timerequest structure, which in turn we will use to
open a timer device.  For now, we will make everything priority 0.
	These routines should be declared as functions returning something to
make life easier as we write the program.  Some, like CreatePort(), always
return the address of a MsgPort structure, so we can declare them
globally. Others, like CreateExtIO() return the address of differently
sized blocks, depending on what we are doing, so they should be cast as
the type we need as we call them.
	We also have to remember to free up the memory when we are done, and
the routines DeletePort() and DeleteExtIO() will do that for us.  After
all this discussion, the problem is again beginning to look simpler.  We
will eventually fix things so they will get simpler yet.  These routines,
by the way, are described in RKM:Libraries and Devices, Library Summaries.
	So here it is:

/***** Tick.c ***********************************************************/

#include "exec/types.h"
#include "devices/timer.h"

struct MsgPort *CreatePort();

void
main()
{
	struct MsgPort *TP;
	struct timerequest *TR;
	int error;

	TP = CreatePort(NULL, 0);
	if (TP == NULL) {
		printf("Not enough memory for the Message Port\n");
		exit(0);
	}

	TR = (struct timerequest *)
					CreateExtIO(TP, sizeof(struct timerequest));
	if (TR == NULL) {
		printf("Not enough memory for the timerequest\n");
		DeletePort(TP);
		exit(0);
	}

	error = OpenDevice("timer.device", UNIT_VBLANK, TR, 0);
	if (error > 0) {
		printf("The timer won't open\n");
		DeleteExtIO(TR, sizeof(struct timerequest));
		DeletePort(TP);
		exit(0);
	}

	TR->tr_node.io_Command = TR_ADDREQUEST;
	TR->tr_time.tv_secs = 5;
	TR->tr_time.tv_micro = 0;

	DoIO(TR);

	printf("Tick\n");

	CloseDevice(TR);
	DeleteExtIO(TR, sizeof(struct timerequest));
	DeletePort(TP);

}

/***********************************************************************/

	You did read the section RKM: Timer Device didn't you?  So you know
what a UNIT_VBLANK is, and that the constants in capital letters are
defined in the appropriate include files.  I also included error checks
in a crude way for good programming practice.  Compile this and play with
it a minute or two.
	All we have done here is allocate memory for the two structures that we
use, initialized them with our values, requested the system to link them
into the appropriate lists, and waited.  Five seconds later, our routine
wakes up, prints a "Tick" message, and exits.  If you don't believe that,
change the value of TR->tr_time.tv_secs to about ten or fifteen, recompile
it, and RUN Tick.  A new CLI will be created, and for the time you have
called for you will be able to perform a DOS command like Date in your
original CLI.  In the time you have specified, the "Tick" message will
appear and the background CLI will end.  You've been multitasking!  The
trouble is that the manuals don't clearly show how you can accomplish
that from within a program.
	You have read the RKM: Exec, Input/Output so you saw the mention of
DoIO() and the other functions for doing IO.  In the program we tried, the
function DoIO() was used to make things simple.  We built the appropriate
structures and DoIO() requested the timerequest structure to be linked
into a message port somewhere.  Did you notice in your earlier perusal of
the exec/ports.h file that the last item in a struct MsgPort is a struct
List?  Not a pointer to a structure, but an actual structure.  The ln_Pred
and ln_Succ fields of your timerequest structure are changed by Exec to
point to the proper elements of that list.  When your time is up Exec
changes the pointers to link the message (timerequest structure) onto the
MsgPort you called for in CreatePort().  DoIO() sees there is mail, sets
any error codes or flags, and exits.  We will be doing quite a bit with
this timer program, so it would be better if we had more convenient
routines to handle allocation and deallocation for us, as well as setting
and stopping a timer.  So, before we continue, let's build a file of timer
utilities to help us.  You might want to modify these somewhat to suit your
specific needs.  In particular, SetTimer() does not now set the
microseconds part of the timerequest so only even second periods are
available.  If you wanted, you could change the formal variable to a double
and do the math to get microseconds right in the routine.
	An important note about compilers:  The stack checking routines various
compilers put into the code will not work in task code.  Since we expect to
be using these routines within tasks, they and any of the following routines
which will become part of task code must be compiled with stack checking
disabled.  There is a further consideration if you are using Lattice v4.0:
When task code is added to the system task list, register A4 is not
preserved, thereby destroying the code's reference to its data sections.
There is a compiler option to cause it to generate register saving code at
the beginning of each function call.  It costs only 24 bytes per function,
and is necessary only for functions referenced by AddTask() or
CreateTask() (q.v.).  It's unlikely any of these would be, but you never
know.  For Lattice 4.0 the compiler command line would be:

	lc -v -y TimerUtilities

	Carefully check your compiler documentation for any special treatment
required for task code.

/***** TimerUtilities.c *************************************************/
/*																								*/
/*		A package of utilities to control timer devices.	This package	*/
/* contains:																				*/
/*																								*/
/*		CreateTimer(unit, priority)													*/
/*			returns: Pointer to a struct timerequest or zero if trouble		*/
/*		AbortTimer(timerequest)															*/
/*			returns: Nothing																*/
/*		SetTimer(time)																		*/
/*			returns: Nothing																*/
/*		DeleteTimer(timerequest)														*/
/*			returns: Nothing																*/
/*																								*/
/************************************************************************/

#include "exec/types.h"
#include "devices/timer.h"

extern struct MsgPort * CreatePort();

struct timerequest *
CreateTimer(unit, priority)
	ULONG unit;
	LONG priority;
{
	struct MsgPort *TP;
	struct timerequest *TR;

	if ((TP = CreatePort(NULL, priority)) == NULL)
		return(NULL);

	if ((TR = (struct timerequest *)
			CreateExtIO(TP, sizeof(struct timerequest))) == NULL) {
		DeletePort(TP);
		return(NULL);
	}

	if (OpenDevice(TIMERNAME, unit, TR, 0) != NULL) {
		DeleteExtIO(TR, sizeof(struct timerequest));
		DeletePort(TP);
		return(NULL);
	}

	return(TR);
}

void
AbortTimer(T)
	struct timerequest *T;
{
	if (AbortIO(T) == NULL) {
		Wait(1 << T->tr_node.io_Message.mn_ReplyPort->mp_SigBit);
		GetMsg(T->tr_node.io_Message.mn_ReplyPort);
	}
}

void
SetTimer(T, time)
	struct timerequest *T;
	int time;
{
		AbortTimer(T);
		T->tr_node.io_Command = TR_ADDREQUEST;
		T->tr_time.tv_secs = time;
		T->tr_time.tv_micro = 0;
		SendIO(T);
}

void
DeleteTimer(TR)
	struct timerequest *TR;
{
	struct MsgPort *TP;

	if (TR != 0) {
		AbortTimer(TR);
		TP = TR->tr_node.io_Message.mn_ReplyPort;
		CloseDevice(TR);
		DeleteExtIO(TR, sizeof(struct timerequest));
		DeletePort(TP);
	}
}

/************************************************************************/

	Compile these, but don't link them.  There's nothing to link to yet.
Notice the routines don't use DoIO().  They use SendIO() instead.  That
will allow us to use them in a task we will create Real Soon Now.  First,
let's write a test routine to see if everything works.  We'll include an
argument this time so we will be able to set the timer to anything we
want.  We'll use the function WaitPort() to wait for completion of the
timer.  This gets us closer to a full multitasking program.  The reason it
isn't full multitasking is that we aren't doing anything else while
waiting for a message (the timerequest structure) to arrive at our message
port.

/***** Test.c ***********************************************************/

#include "exec/types.h"
#include "devices/timer.h"

struct timerequest *CreateTimer();

void
main(argc, argv)
	int argc;
	char *argv[];
{
	struct timerequest *T;
	struct MsgPort *P;
	int delay;

	if (argc != 2) {
		printf("Please provide a delay time\n");
		exit(0);
	}

	delay = atoi(argv[1]);

	T = CreateTimer(UNIT_VBLANK, 0);
	P = T->tr_node.io_Message.mn_ReplyPort;
	SetTimer(T, delay);
	WaitPort(P);
	printf("Tock\n");
	DeleteTimer(T);
}

/************************************************************************/

	Compile this and link it with TimerUtilities.o to get a complete
program.  Alternatively, you could combine both files together just for
test purposes.  Now run the test program, remembering to include a delay
value: Test 3, for example.
	Allright! The utility package is quite a help.  Now, look carefully at
AbortTimer().  A brief mention should be made here about the function
AbortIO().  It isn't described in some books.  If the IO has completed
when AbortIO() is called, it returns a -1 from a timer device (or  a
meaningless value from some other devices), and no message is attached to
the message port.  If the IO has not completed and is aborted, the
function returns a 0 and the timerequest structure (our message) is
attached to the message port.  We then call GetMsg() to remove it from the
port, something unnecessary in this application, but possibly required in
others that use this routine.  We will see more of GetMsg() later.  The
way AbortTimer() waits for completion is Wait().  This function waits for
a particular bit to be set.  While waiting, of course, just as in the case
of WaitPort() and WaitIO(), Exec can be doing other things.  To see that
effect, try Running several copies of Test at the same time: Run Test 30
<CR> Run Test 20 <CR> Run Test 10 <CR>.  You should see the word "Tock"
printed three times, ten seconds apart.
	I said Wait() waits for a particular bit to be set.  This bit is called
a signal, and every message port gets one allocated.  They have to be
allocated because for every task, like the one we are running when we run
Test, has a longword (32 bits) in a structure available for signalling.
Each bit has to have a unique meaning.  Exec takes the lower sixteen for
itself, leaving the upper sixteen available to us.  We don't know which
bits have been allocated already, or to what, so we use a routine
AllocSignal() to find out our bit number.  The signal must be freed with
FreeSignal() when we don't need it any more.  These details have been
taken care of for us by CreatePort() and DeletePort().  The point is that
there is a number in a message port that is the number of a bit (from 0 to
31) that says when this bit is set in a certain location, mail has arrived
at the port.  It stays set until you Wait() for it in some fashion or
another ( WaitPort() or WaitIO() ).  We can recreate that bit by shifting
a 1 left the number of times equal to the signal number.  The signal
number is in a struct MsgPort, element mp_SigBit.  Remembering that in our
timerequest structure there is an IORequest structure containing a
Message structure which contains a pointer to a message port, the
reference is mn_ReplyPort->mp_SigBit.  Since that Message structure is
part of an IORequest structure we have to say
io_Message.mn_ReplyPort.mp_SigBit.  That IORequest in turn is a named part
of a timerequest structure called tr_node.  So, we obtain the number,
shift a 1 left that number of times, and Wait() for it to be set, which
yields the mouthful operation:

Wait(1 << T->tr_node.io_Message.mn_ReplyPort->mp_SigBit);

	We won't say that very often, but we will be using Wait() on specific
bits.
	Let's review what we've done.  We allocated and initialized a MsgPort
structure.  That sits off to the side as our mailbox.  We allocated and
initialized a timerequest structure, among other things putting the
address of our mailbox in it so the system will know where to send the
structure back to when it's done with it, a return address, if you like.
That's our message.  We put some stuff in the message (the command and
time values), sent it off to Exec, and waited for mail to arrive at our
mailbox.  Because of the particular functions used for waiting, the
microprocessor can go to sleep if Exec isn't doing anything else.  We
could have done busy loop waiting, which consists of looking at some value
in the MsgPort to see if it has changed, and if it hasn't, looking again.
That kind of program won't quite choke up the machine, depending on the
priority it has, but it eats up all the spare processor time and can make
some operations very slow.  We will demonstrate that with our timer
routines by and by.
	We have been operating as a task spawned by the CLI (A process,
actually, but that is a sort of super task run by DOS.  More on that
later).  We communicated with the rest of the system by sending messages
and looking for signals.  You cannot write a function and call it with
values as in ordinary c programming and have it be a separate task.  You
must use the message passing system.  Exec, whose name we have been
invoking all along, is primarily a program which manipulates lists, lists
of messages being a large part of a busy program.  It is properly called
the executive.  Every time a clock interrupt occurs, Exec is activated,
and it goes looking for things to do by scanning various lists.  You
don't ever call Exec, you just readdress a message.  Exec will know about
it soon enough and take suitable action.
	Let's examine lists more closely (You have been diving into the RKM to
look things up all along, haven't you?  See RKM: Exec, Lists).  A list is
a simple little structure of three pointers and a byte defining its type:

LONG lh_Head		; points to the first node in the list
LONG lh_Tail		; is always zero
LONG lh_TailPred	; points to the last node in the list
BYTE lh_Type		; defines the types of nodes in the list
BYTE lh_pad			; is here to make the structure an even number of bytes

	The lh_Type field signifies what kind of a list it is.  Those
definitions are in the include file exec/nodes.h, and can be tasks,
interrupts, memory, all the things that Exec keeps track of.
	If the list is empty the lh_Head element points to its lh_Tail and the
lh_TailPred points to its lh_Head.

        ---- lh_Head <------
        |                  |
        ---> lh_Tail = 0   |
                           |
             lh_TailPred ---

				Figure 1

	Now if a node is added to the list, the list's head is made to point to
the node, that is, the first location of the node.  The node has no
successors, so the ln_Succ field is made to point to the list's lh_Tail,
which is always zero so a search routine has a place to stop.  The node's
predecessor is the list, so that field is made to point to the first
location of the list.  As more nodes are added on to the list, the ln_Succ
field is made to point to the first location of the following node and the
following node's ln_Pred field is made to point to the first location of
the preceding node.  That way all nodes are linked forwards and backwards.
A list and the nodes linked into it could be represented like this:

                                     Node:
                              -------> ln_Succ -------<-|
                              |   ---- ln_Pred       |  |
              List:           |   |    ln_Type       |  |
                lh_Head -------<---    ln_Pri        |  |
         (zero) lh_Tail <--------      ln_Name       |  |
                lh_TailPred --- |                    |  |
                              | |                    |  |
                              | |    Node:           |  |
                              | |      ln_Succ <======<-+--|
                              | |      ln_Pred ------+--|  |
                              | |                    |     |
                              | |                    |     |
                              | |    Node:           |     |
                              |_====>  ln_Succ <-----|     |
                                       ln_Pred ------------|

										Figure 2

	A node has a type, just like a list, and generally agrees with the type
of list it is in.  It also has a priority so that Exec can establish the
order in which it will process a node.  There is a name field which can be
a pointer to a character string such as "Initial CLI" to give the
structure an identifier that can be searched for without knowing which
list it is in.
	Nodes by themselves aren't of any use, but they are the beginning
structure of almost every structure in the system.  Recalling the
discussion of messages and the timerequest structure, you can see how
messages can be sent to a message port just by finding where the
lh_TailPred field of the message port points, putting that address in the
ln_Pred of the message, and changing the contents of that address to point
to the first location of the message.  Since it will be the latest message
attached to the port, its ln_Succ field will be filled with the address of
the lh_TailPred field of the message port.  Exec has to change just those
three addresses and three more in the list the message came from, if it
was in one, to send it, no matter how large the message.  You don't have
to bother yourself with these details as there are routines that perform
these functions, allowing you to think of "sending" or "putting" a message
somewhere and waiting for a "reply".  The list structure comes more
readily to mind when we link something into one, such as a task (See RKM:
Exec, Tasks and include/exec/tasks.h).
	Tasks are jobs that Exec performs when the conditions warrant, such as
a timer running out, or a key being pressed.  There are certain
limitations to tasks as such.  They are procedures which cannot be called
by another function (Exec does that when the time is right, and it is best
not to interfere with the opertion of Exec.  It bytes).  A task cannot
return a result, and as you shall see, should probably not return at all.
A task cannot call any DOS related input or output functions that require
multitasking like printf(), although way down the line I'll give some
pointers on accomplishing those functions.
  Communication with a task is done by message passing.  There are
standard system messages like we have been using (the timerequest
structure), and you can construct messages to suit your taste as long as
Exec understands them (the beginning structure is a Message structure) and
your task understands them (the remainder of the structure contains
information meaningful to it).  There is a special kind of message called
a semaphore, used to provide a means of mutual exclusion, and
communication is also done by signals, which are single bits of an
unsigned longword. One other means not supported by Exec is by global
variables.  If your task can see a variable changed by another program,
your task can act on it.  However, a very important point for a
multitasking system, you must not busy wait in a task as you will eat up
all the spare time the microprocessor has.  Notice I said spare time and
not necessarily all its time.  That depends on your task's priority.  Busy
waiting is exemplified by this:

	s = 0;
	while (s != 999999999) {
		s = s + 1;
	}

Later on we will try a busy waiting routine to see what happens.
	Signals are the simplest executive system for intertask communication.
Every task has available an unsigned longword called tc_SigAlloc.  I'm
sure you have your RKM:Exec book open to the include/exec/tasks section
this very moment, so have a look at the other signal related elememts
tc_SigWait and tc_SigRecvd.  Signals are single bits of the longword
tc_SigAlloc.  As I mentioned earlier in the discussion of Wait() the lower
sixteen are reserved for use by Exec.  The upper sixteen are available for
your use. You could define a certain bit as meaning a certain thing to
your task, but the burden of maintaining that bit is large.  You would
have to keep a copy in your main program, one in your task, and provide
one for Exec so it would know what to do if that signal bit was set or
cleared.  The overhead is taken care of neatly by the functions
AllocSignal() and FreeSignal().  AllocSignal() returns a number which is
the number of times a 1 bit must be shifted left to obtain the signal bit.
FreeSignal() takes that number as its argument, so it must be saved until
FreeSignal() is called.  You can specify which bit you want allocated by
providing AllocSignal() with the bit number, but in the case of several
possible paths and times of signalling a task, the preferred method is to
call AllocSignal(-1), which returns the next signal number available.  If
no signal is available, the function returns a minus one.  If a signal is
allocated, that bit is set in the longword tc_SigAlloc.  Would you like to
see that?  I said a program run from the CLI could be seen as a task.  You
can find your own task structure by calling FindTask(0), which returns the
address of a task structure.  Well, a process structure, really, but a
process structure starts off with a task structure and structures are
nothing more than a representation to the programmer of a bunch of
locations in contiguous memory.  FindTask() returns an address, and if we
tell the compiler that address is a task structure, it will believe us.
The snare and delusion is that block of memory must have been built as a
task structure originally or our results will be meaningless.  It was
built that way.  Trust me.

/***** Tasking.c *********************************************************/

#include "exec/types.h"
#include "exec/tasks.h"

void
main()
{
	struct Task *T;							/* Declare your variables */
	unsigned int s;							/* Sounds like eat your vegiables */

	T = (struct Task *) FindTask(0);		/* Find out where we are  */
	printf("%08x\n", T->tc_SigAlloc);	/* Print our allocated signals */
	s = AllocSignal(-1);						/* Get another one */
	printf("%08x\n", T->tc_SigAlloc);	/* Print the allocated signals */
	FreeSignal(s);								/* Give it up */
	printf("%08x\n", T->tc_SigAlloc);	/* Print the allocated signals */
	printf("%d\n", s);						/* Print the bit number */
}

/*************************************************************************/

You should have obtained the results 0000ffff, 8000ffff, 0000ffff, and 31.
If you didn't you shouldn't be reading this.  It's wrong.  Anyway, we see
that the most significant bit was allocated, then deallocated.  The bit
number is 31 decimal, which agrees with the hexadecimal printout of the
bits themselves, and we see all lower sixteen already allocated.
	If we make a task, the signals we expect to receive must be allocated
within that task, not by another one, so that will be part of the
initialization.  We must also inform other tasks just what bit we expect to
receive for a particular action.  We must use one of the methods described
before, namely message passing, signalling (rather tricky in this case; a
problem left as an exercise for the student), or global data.  We'll use
the last method in this demonstration because the programs are small.
(There is another way if a message port is constructed:  Give the port a
name, let the other task find it with FindPort("name"), then get the
mp_SigBit of that port.)
	Now that you know something about signals, you must know how to deal
with them within the task.  I mentioned waiting before.  The Wait()
function is the essential part of multitasking as the programmer sees it.
Wait() causes the particular bits specified in its argument to be put into
the tc_SigWait element of the task structure and calls Exec.  Until that
bit gets set by some other task, Exec can give the processor to some other
task.  Your task is essentially halted.  When that bit gets set, Exec moves
your task to the ready queue (attaches your task structure to the TaskReady
list) in order of its priority.  When all higher priority tasks have run
and gone into the wait state (have been attached to the TaskWait list),
yours will get processor time.
	WaitPort() and WaitIO() are alternate methods of waiting, as I mentioned
before.  Both can wait for one event.  True, WaitPort() could be waiting
for messages from different sources, but it is still waiting for the SigBit
of that one port to be set, indicating message arrival.  Wait() can wait
for as many as sixteen events at a time.  Thirty-two are possible, but
remember the reservation of sixteen bits by Exec.  The reason is that the
argument supplied to Wait() is a mask of bits.  Wait() returns a
collection of bits received.  If you allocated three bits for signals in
your task, calling them x, y, and z, you would use the form:

	ULONG signals, x, y, z;

	signals = Wait( x | y | z );
	if (signals & x) {
		/* Do job x */
	}
	if (signals & y){
		/* Do job y */
	}
	if (signals & z){
		/* Do job z */
	}

The jobs that are done can be separately defined procedures.  Those
procedures take on the priority of your task because they are using
processor time while your task is active.  They can operate at another
priority if a task of a different priority calls them.  A small point, but
one worth remembering.
	What makes a program of such a form a task is the Task structure (RKM:
Exec, Tasks) that we allocate for it which contains information Exec needs
to run it, like the signals allocated for it we played with before, the
stack it will use, and so on, and a call to the function AddTask(), which
informs Exec of the address of the program.  There are many variations on
the use of the structure, so here we will use the functions CreateTask()
and DeleteTask() which take care of the details in a simple but useful way.
The structure will be linked into the system task list in the way I
discussed in the section on lists.
	I have discussed task initialization well enough to make a small task,
that is, the startup part of the task itself, and the body of the task and
waiting.  The other essential part is ending a task.  Recall I said a task
should probably not exit at all.  The reason comes from the way a task is
created.  You allocate space for a task structure, fill in various fields,
including the address of your task code and the size of the stack the task
will need, and ask Exec to link the structure into its task list.  After
you give the task a chance to run (that doesn't happen until your main
program waits) there will come a time to exit.  When you do you must either
leave the task in memory, still operating, or inform Exec that it is no
longer needed, at which time Exec will deallocate the task's stack, memory
attached to the task structure (another whole subject we won't need at the
moment, see RKM:Exec, Memory Allocation), and the task structure itself.
If your task exits on its own for some reason, with your main program still
running, Exec will go through the deallocation process without informing
your main program.  When your main program exits, attempting to deallocate
the same task, Exec will become strange.  You might not see an effect until
another program is run, at which time the Guru may visit you, or there may
be the AMIGA_FIREWORKS_DISPLAY.  So responsibility for task cleanup must be
thought out beforehand.  If your task is to end with the end of the main
program, then the task must not exit on its own without informing your main
program.  If it is to survive the ending of your main program, then the
task itself must deallocate any resources such as close open libraries,
deallocate memory other than that in the task's stack and memory lists
(tc_MemEntry), close devices, and turn off the lights before the iceman
comes.  In our first example we will put responsibility of deallocation in
the main program, so we won't let the task exit.  We do that with the
Wait() function.  Remember that Wait() waits for some bit of a mask, or
collection, of bits to be set.  If we don't specify a mask, then the task
will Wait() forever.  This isn't busy waiting: remember that Wait() gives
Exec control of the microprocessor.  Wait(0) is sufficient.  Later on we
will leave the task running after the main program exits and have it
terminate on some external event.
	It is helpful to have a free memory checking routine available while
you're playing with programs of this kind to see the room your program
takes, and more importantly, that everything is returned to free memory
after your program exits.  Avail in its various incarnations is perfectly
good for that.
	Here we use the functions you compiled already.  Don't forget to link
that object file with these two when you compile.  The first program is
the task itself: in this case one that will provide a stream of "ticks" at
one second intervals. It first allocates a signal the main program will
use to tell it when it is no longer needed, then creates a timer,
discovers the signal bit it uses so we know when the timer has expired,
and sets the timer to get it running.  Then it waits, either for a tick or
an abort signal.  If it is a tick it immediately resets the timer and
signals the main program whose identity we know from the global
declaration of MT, and then loops back to waiting for another event.  If
it receives an abort signal it deallocates the abort signal bit and the
timer structure, signals the main program it has finished, and waits to be
deallocated itself.	A priority for the timer is available from the main
program as a global variable that will be of use when experimenting.  Since
this is going to be used as a task, you must compile it with stack checking
disabled and register preserving code generation enabled.

/***** TimeTick.c ********************************************************/

#include "exec/types.h"
#include "devices/timer.h"

extern struct timerequest *CreateTimer();
extern void SetTimer();
extern void DeleteTimer();

extern struct Task *MT;
extern unsigned int ready;
extern unsigned int tick;
extern int priority;

unsigned int abort;

void
TimeTick()
{
	struct timerequest *T;
	int ab;
	unsigned int t;
	unsigned int signals;

	ab = AllocSignal(-1);
	abort = 1 << ab;

	T = CreateTimer(UNIT_VBLANK, priority);
	t = 1 << T->tr_node.io_Message.mn_ReplyPort->mp_SigBit;
	SetTimer(T, 1);

	for(;;) {
		signals = Wait(t | abort);
		if (signals & t) {
			SetTimer(T, 1);
			Signal(MT, tick);
		}
		if (signals & abort) {
			FreeSignal(ab);
			DeleteTimer(T);
			Signal(MT, ready);
			Wait(0);
		}
	}
}

/*************************************************************************/

	Now for the main program: It provides the timer with the address of
this task and a bit the main program expects to see as a "ready" signal,
used for the ticks themselves and as a signal that the abort procedure has
completed when it comes time to deallocate the task.  It installs the task,
waits for a few ticks, printing out a message on the screen at each tick,
then deletes the task.  Compile it, link it with the object files you got
by compiling TimeTick.c and TimerUtilities.c, and run it.

/***** ShowTicks.c *******************************************************/

#include "exec/types.h"
#include "exec/tasks.h"

extern struct Task *FindTask();
extern struct Task *CreateTask();

extern void TimeTick();

extern unsigned int abort;

struct Task *MT;
unsigned int ready;
unsigned int tick;
int priority = 0;

void
main()
{
	struct Task *T;
	int r, t;
	int i;

	MT = FindTask(0);
	r = AllocSignal(-1);
	ready = 1 << r;
	t = AllocSignal(-1);
	tick = 1 << t;

	T = CreateTask("Ticks", priority, TimeTick, 4000);

	for (i = 0; i < 10; i++) {
		Wait(tick);
		printf("Tick\n");
	}

	Signal(T, abort);
	Wait(ready);
	FreeSignal(r);
	FreeSignal(t);
	DeleteTask(T);

}

/*************************************************************************/

	Note that when CreateTask() is called, Exec links the task into its task
list, and if it has a priority higher than than the program that spawned
it, the task will begin execution before the spawning program retains
control of the microprocessor.  If the priority is lower, the task will not
run until the main program gives up control of the microprocessor with a
Wait() instruction (or some other form of Wait() such as WaitIO()).  In
this case it makes no difference because immediately after CreateTask() is
called, Wait() is called to wait for tick events.  Note, too, that after
the task is signalled to abort, the main program Waits for a ready signal
assuring that the task has gone through its abort procedure.  If the
priority of the task was lower than that of the main program and the
Wait() was not present, the main program would continue to have control of
the microprocessor until it exited.  The last instruction is DeleteTask(),
so that would be performed before the task ever saw the abort signal.  The
timer would be left in memory with no easy way to later delete the memory
it occupies.  The important thing to keep in mind is Signal() can cause
Exec to halt the calling program and run the signalled task before
returning to the calling program.  Fine points like this are not
immediately obvious in source code.  I provided the global int priority so
you could experiment with it.  For reference, the priority of a program run
from CLI is zero unless you've changed it.  It would be helpful to add code
to accept a priority from the command line (argc & argv).
	Now try a busy waiting program to investigate the Amiga's behavior.  It
is easy enough to avoid such a programming practice, but the real value of
doing such an investigation is learning the effects of tasks which take up
a good deal of processor time. Let's set up two tasks: one for giving time
ticks and one for trying to occupy all of the processor for a while. We'll
make a hog task and a new main program, but use the same TimeTick with a
temporary modification and the same TimerUtility routines.  Modify
TimeTick() by declaring an extern int count, and after the SetTimer() call
in the Wait() loop but before the call to Signal(MT, tick), increment count
with count++;.  This agrees with the declaration of count in ProcHog() and
provides the means for determining the relative actions of the two tasks.
The result is printed out as the main program exits.
	The main routine expects two priority values to be provided: ProcHog 2 1
for example.
	The function BusyWait() will be installed as a task, so you must compile
this with stack checking disabled and register preserving code enabled.

/***** ProcHog.c *********************************************************/

#include "exec/types.h"
#include "exec/tasks.h"

extern void TimeTick();
extern LONGBITS abort;

struct Task *MT, *FindTask(), *CreateTask();

int count = 0;
int priority;
LONGBITS ready, tick;

void
BusyWait()
{
	int i;

	for (i = 0; i < 1000000; i++);

	Signal(MT, ready);
	Wait(0);
}

void
main(argc, argv)
	int argc;
	char *argv[];
{
	struct Task *T, *H;
	int r, t;
	LONGBITS signals;

	if (argc != 3) {
		printf("Please provide priority values for TimeTick and BusyWait\n");
		exit(0);
	}

	MT = FindTask(0);
	r = AllocSignal(-1);
	ready = 1 << r;
	t = AllocSignal(-1);
	tick = 1 << t;

	T = CreateTask("Ticks", atoi(argv[1]), TimeTick, 4000);
	H = CreateTask("Hog", atoi(argv[2]), BusyWait, 4000);

	for (;;) {
		signals = Wait(tick | ready);
		if (signals & tick) {
			printf("Tick\n");
		}
		if (signals & ready) {
			Wait(tick);
			Signal(T, abort);
			Wait(ready);
			DeleteTask(T);
			DeleteTask(H);
			FreeSignal(r);
			FreeSignal(t);
			break;
		}
	}
	printf("Ticks counted: %d\n", count);
}

/*************************************************************************/

	Here I used the exec/types typedef LONGBITS to replace "unsigned int" to
clarify the role of the variable.  BusyWait() is the task designed to hog
the microprocessor, and main(), of course, is the task spawning program
running at priority 0.  If you invoke ProcHog 2 1 so that both tasks run at
a higher priority than the main program with the timer running at a
priority higher than BusyWait(), here's what happens: At creation of task T
("Ticks") the timer is started, that is, TimeTick is set to running
immediately.  It will be a full second before the timer runs out, so as
soon as Wait() is reached in TimeTick(), control is returned to the main
program, which proceeds with creating the task H ("Hog").  Since its
priority is also higher than that of the main program it begins running
immediately, preventing control from returning to the main program for
about ten seconds.  Meanwhile, every second TimeTick() sets the signal
"tick", but it is not seen by the main program because it is prevented from
running by the looping of BusyWait().  When BusyWait() finally quits and
sets the signal "ready" the main program can run.  The main program sees
that the signal "tick" has been set (remember signals get cleared by Wait()
so there is no way of telling how many signals there were, just that there
was at least one) and prints the word "Tick".  It also sees that "ready"
was set so it signals TimeTick() to abort itself.  The reason for waiting
for one more "tick" will become apparent when we change the priorities of
the tasks.  After the main program signals TimeTick() to abort it waits for
the ready signal to ensure TimeTick() has deallocated its resources before
continuing for the reasons discussed above.  If both are run at the same
priority above that of the main program the same thing happens, although
in this case Exec doesn't run TimeTick() for the same reason.  Here you see
the effect of the Exec "quantum" which allows a task to run for a certain
length of time whereupon Exec halts the running task to examine its task
list to see if there are any of higher priority that need to run.  There
are none in our program suite, but Exec places the task just halted at the
bottom of the list of tasks at that same priority so others of the same
priority can run for a while.  The effect is that TimeTick() and BusyWait()
each get to run.  Since both are still at a higher priority than the main
program, they will keep control of the microprocessor until BusyWait()
expires.  As you can see, busy waiting doesn't exactly stop everything in
the Amiga, but it can interfere with lower priority tasks.
	Now if you run ProcHog 1 2 so that both tasks have a higher priority
than the main program, as before, but with BusyWait() at a higher priority
than TimeTick(), much the same thing appears to happen, but in this case
TimeTick() runs down to its Wait() when created as a task, and is prevented
from doing more until BusyWait() expires, when it again gets control and
sees that at least one second has passed.  It resets the timer and waits
again.  The main program regains control, waits for another tick, aborts
TimeTick(), and exits, so only two tick events are recorded instead of ten.
	If you run all three at the same priority by invoking ProcHog 0 0, then
the quantum effect allows the main program to share the processor equally
with the two spawned tasks, and each tick event is shown by the main
program as the word "Tick".  Eleven are counted instead of ten because the
main program waits for a tick before aborting TimeTick().  If you invoke
ProcHog -1 -2 to put both tasks below the priority of the main program then
the main program keeps control until it waits.  TimeTick() runs as needed
because the main program is waiting most of the time, taking only enough
processor time to print the word "Tick", and BusyWait() gets whatever time
is left over.
	The final case is both tasks having a lower priority than the main
program but with BusyWait() having a higher priority than TimeTick() (e.g.
ProcHog -2 -1).  You see that the word "Tick" is never printed, and that
TimeTick() has recorded only one event.  Here is the sequence of events:
The main program creates the task "Tick" (TimeTick()), but since the task
has a lower priority than that of the main program, it does not yet run.
The main program then creates the task "Hog" (BusyWait()), which also has a
lower priority so does not run.  The main program keeps control until it
waits for a signal (tick or ready).  BusyWait(), having the next lower
priority, takes control of the microprocessor and keeps it until the loop
is exhausted, whereupon it signals the main program with "ready".  The main
program then proceeds to abort TimeTick().  TimeTick() has never had a
chance to even initialize!  If we tried to signal an abort the bit would be
undefined (zero, actually, since it is declared an extern, and external
variables are supposed to be initialized to zero at compile time) because
it is defined within TimeTick().  Remember, signals must be defined within
the task which expects to receive them.  So, we would signal TimeTick()
with nothing and wait for a ready from it.  As soon as we waited,
TimeTick() would get control, proceed with counting ticks, signalling the
main program with "tick" each time, but the main program would ignore it
because it would be waiting for "ready".  The main program would hang up.
By waiting for a tick at this point, we can be sure that TimeTick() has
initialized, that "abort" has been defined and that TimeTick() will respond
to it.
	Now is the time to put all this into useful programs.  A simple
demonstration is a little digital clock that can fit in the title bar of an
existing window.  We can obtain the date and time in seconds from January
1, 1978 by reading the system clock with the timer.device command
TR_GETSYSTIME.  A little arithmetic massaging will yield the time of day.
We can use the same kind of timer as we did TimeTick() to provide a signal
to update the clock.  The display can be a window the same height as a title
bar.  I won't get into much of a discussion of windows here because of the
thorough treatment already given in the Intuition Reference Manual.  It
would probably be helpful to read this source code while referencing that
manual to gain an understanding of practical window implementations.
	The function main() opens the little window in the title bar of the
Workbench screen, sets the colors to use, makes a one second repeatable
timer,  and waits for either a tick signal or a signal that you clicked on
the close box of the clock window.  To avoid complexity, main() will exit
on reception of any signal from the window, but because of the way
NewWindow is defined, the only signal Wait() can get is that from a click
on the close box. Notice the loop for Wait() and decoding of signals is
defined by the macro FOREVER.  This is a definition in
intuition/intuition.h of the function for(;;) which helps clarify the
source code.
	The function cia() is just a routine to convert an integer to a two
digit ASCII string and place it in a string buffer for the display.
	Notice that the one second ticks are used only as an alert to main() to
read the clock, rather than being used as actual clock ticks.  The reason
for this is that in a multitasking system, the ticks will not necessarily
come at exact one second intervals depending on other system activities
(tasks), such as resizing other windows and so forth.
	Compile and link this by itself.  I purposely didn't use any functions
in TimerUtilities.c to make it small.  Also, I put main() before cia() and
commented out the two lines referring to SysBase because an experiment soon
to come will need that order and that reference.
	Try Clock as a command, and also try Run Clock several times to see
several invocations of it.  The clock display itself is in the drag bar of
its own window so you can move one out of the way to uncover another.  You
might use Avail to see how much memory each invocation takes, for future
reference.  I will be discussing the reasons for that later on.
Compilation does not now need any special options.

/***** Clock.c ***********************************************************/

#include <exec/types.h>
#include <intuition/intuition.h>

extern struct Window *OpenWindow();
extern struct Task *FindTask();
extern struct MsgPort *CreatePort();
extern struct IntuiMessage *GetMsg();
extern struct timerequest *CreateExtIO();

/* Use this when compiling as NewClock.c */
/* struct ExecBase *SysBase; */

struct IntuitionBase * IntuitionBase;
struct GfxBase * GfxBase;

char timestring[9];	/* The ASCII time buffer */
BYTE t_i;				/* The buffer index */

struct NewWindow NewWindow = {
	489, 0, 150, 10, 3, 2, CLOSEWINDOW,
	WINDOWCLOSE | WINDOWDRAG | WINDOWDEPTH | BORDERLESS,
	NULL, NULL, NULL, NULL, NULL, 0, 0, 0, 0, WBENCHSCREEN
};

void
main()
{
	extern void cia();

	struct Window *W;
	struct IntuiMessage *IM;
	struct timerequest *TR;
	struct MsgPort *TP;
	struct Task *T;
	LONGBITS t;
	LONGBITS w;
	LONGBITS signals;
	int secs, h, m, s;

/* Use this when compiling as NewClock.c */
/*	SysBase = *((struct ExecBase **) 4);  */

	IntuitionBase = (struct IntuitionBase *)		/* Open the window */
							OpenLibrary("intuition.library", 0);
	GfxBase = (struct GfxBase *)
							OpenLibrary("graphics.library", 0);
	W = OpenWindow(&NewWindow);
	w = 1 << W->UserPort->mp_SigBit;
	SetAPen(W->RPort, 2);
	RectFill(W->RPort, 27, 0, 98, 9);
	SetAPen(W->RPort, 3);
	SetBPen(W->RPort, 2);

	T = FindTask(0); 		/* Make the timer priority the same as this task */
	TP = CreatePort(NULL, T->tc_Node.ln_Pri);		 /* Make a timer */
	TR = CreateExtIO(TP, sizeof(struct timerequest));
	OpenDevice(TIMERNAME, UNIT_VBLANK, TR, 0);
	t = 1 << TP->mp_SigBit;
	TR->tr_node.io_Command = TR_ADDREQUEST;		/* Start the timer */
	TR->tr_time.tv_secs = 0;
	TR->tr_time.tv_micro = 1000;
	SendIO(TR);

	FOREVER {
		signals = Wait(t | w);
		if (signals & t) {									/* The timer ticked */
			TR->tr_node.io_Command = TR_GETSYSTIME;
			DoIO(TR);
			secs = TR->tr_time.tv_secs;
			s = secs % 60;					/* Extract hours, minutes, seconds */
			secs -= s;
			m = secs % 3600;
			secs -= m;
			m /= 60;
			h = secs % 86400;
			h /= 3600;
			t_i = 0;							/* Initialize the buffer pointer */
			cia(h);							/* Convert values to ASCII */
			cia(m);
			cia(s);
			Move(W->RPort, 30, 7);		/* Display all but the final ':' */
			Text(W->RPort, timestring, 8);

			TR->tr_node.io_Command = TR_ADDREQUEST;	/* Restart the timer */
			TR->tr_time.tv_secs = 1;
			TR->tr_time.tv_micro = 0;
			SendIO(TR);
		}
		if (signals & w) {					/* You clicked on the close box */
			IM = GetMsg(W->UserPort);
			if (AbortIO(TR) == NULL) {
			Wait(t);
			}
			CloseDevice(TR);
			DeleteExtIO(TR, sizeof(struct timerequest));
			DeletePort(TP);
			ReplyMsg(IM);
			CloseWindow(W);
			CloseLibrary(IntuitionBase);
			CloseLibrary(GfxBase);
			break;
		}
	}
}

void
cia(i)
	int i;
{
	timestring[t_i++] = 0x30 + i / 10;
	timestring[t_i++] = 0x30 + i % 10;
	timestring[t_i++] = ':';
}

/*************************************************************************/

	This could be done in a different way by passing messages between two
tasks.  One task will be a window as in Clock.c, and the other will be a
task which gets system time, converts it to a string, and replies to the
message.  We must first define a message structure:  We need a command byte
to tell the time task whether to send a time value or abort itself, and we
need a longword that is a pointer to a string.  The command bytes will be
defined with #define statements.  These #defines and the structure
definition will be put into an include file so they will be available to
any source code using them:

/***** timetask.h ********************************************************/

#ifndef EXEC_PORTS_H
#include "exec/ports.h"
#endif

#define TM_REQUEST 0
#define TM_ABORT   1

struct TimeMsg {
	struct Message	tm_Msg;
	UBYTE				tm_Cmd;
	char *			tm_Tim;
};

/*************************************************************************/

	The task which makes the time string will have the arithmetic routines
of Clock.c, a timer.device to obtain system time, and a message port.  An
important point here is that many different tasks can send messages to the
time task to get the string.  It does not have to be compiled or linked
with the tasks using it because its message port can be found in the system
by name by calling FindPort("TimeTaskPort").  Since it can stand alone it
takes responsibility for its own task cleanup and actually exits, unlike
the previous tasks.  For this reason, DeleteTask() should not be called on
it once you have sent it a TM_ABORT message.  On reception of the TM_ABORT
message the task deletes the timer it uses and scans the message port for
any outstanding messages (including the TM_ABORT message).  If there are
any, it sets the timestring pointer of each to NULL and replies.  It then
deletes the message port and exits. When the message port is deleted,
DeletePort() removes it from the Exec list of named ports and it cannot be
mistakenly found after that.  This routine needs stack checking disabled
and registers saved.

/***** TimeTask.c *********************************************************/

#include <exec/types.h>
#include <devices/timer.h>
#include "timetask.h"

extern struct MsgPort * CreatePort();
extern struct MsgPort * FindPort();
extern struct TimeMsg * GetMsg();
extern struct timerequest *CreateTimer();
extern void SetTimer();
extern void DeleteTimer();

char timestring[9];
int t_i;

void
cia(i)
	int i;
{
	timestring[t_i++] = 0x30 + i / 10;
	timestring[t_i++] = 0x30 + i % 10;
	timestring[t_i++] = ':';
}

void
TimeTask()
{
	struct timerequest *TR;
	struct MsgPort *MP;
	struct TimeMsg *M;
	int secs, h, m, s;

	TR = CreateTimer(UNIT_VBLANK, 0);				/* Make a timer */
	TR->tr_node.io_Command = TR_GETSYSTIME;

	MP = CreatePort("TimeTaskPort", 0);				/* Make a message port */

	for (;;) {
		WaitPort(MP);										/* Wait for mail */
		M = GetMsg(MP);
		if (M->tm_Cmd == TM_REQUEST) {				/* Time to go to work */
			/* The timer command is always TR_GETSYSTIME */
			DoIO(TR);
			secs = TR->tr_time.tv_secs;
			s = secs % 60;					/* Extract hours, minutes, seconds */
			secs -= s;
			m = secs % 3600;
			secs -= m;
			m /= 60;
			h = secs % 86400;
			h /= 3600;
			t_i = 0;							/* Initialize the buffer pointer */
			cia(h);							/* Convert values to ASCII */
			cia(m);
			cia(s);
			M->tm_Tim = timestring;		/* Put the address in the message */
			ReplyMsg(M);
		}
		if (M->tm_Cmd == TM_ABORT) {					/* Boss says quit */
			DeleteTimer(TR);
			do {												/* Tell everybody */
				M->tm_Tim = 0;
				ReplyMsg(M);
			} while (M = GetMsg(MP));
			DeletePort(MP);
			break;	/* Drop out of the function, let Exec delete the task */
		}
	}
}

/*************************************************************************/

	The window we will use is much like the one in Clock.c, but since
TimeTask() does the arithmetic, the window has only to send a request
message to it.  The program creates a message port for replies, a timer as
in Clock.c and a task for the previous program, TimeTask().  We are running
the ports, timers and task at the same priority of the main program, so
when CreateTask() is called, the main program retains control of the
microprocessor.  In order for TimeTask() to initiate, the main program has
to halt briefly.  A handy way to do that here is to Wait() for a tick from
the timer.  As soon as Wait() is called, Exec will give control to
TimeTask(), which will then create its message port and timer, and then
gives up control by WaitPort(), to sit idle until a message is sent to its
port.  As soon as the main program's timer ticks, control will return to
it, whereupon it will be able to find the port that the task created.  It
can then wait for a tick (which signals it to send its message to the task
port), a reply from the task, or a click on the window close gadget.
	When the main program gets a tick, signalled by the bit "t", it makes up
a message to send to the task.  Part of the message initialization was done
right after creation of the reply port.  The initialization is the
references to m.tm_Msg and m_tm_Cmd.  Notice that the message is declared
as a TimeMsg structure (struct TimeMsg m), not a pointer to a structure
(structure TimeMsg *M, for example, for the pointer to the reply).  This
lets the compiler allocate space for the message.  Quite often you will
see a call to AllocMem() to allocate this space dynamically. An example is
in RKM: Exec Appendix B in the source code for CreateExtIO(). This is done
to allow the program itself to deallocate the memory used by the message
when it is no longer needed.  In this case we use the memory until the
program exits, so we let the compiler handle it.
	Refer to the include file exec/ports.h.  You see there that a message
structure is defined as a node, an address to a reply port, and a length.
Our extension of that structure, struct TimeMsg, also has a command byte
and a string address.  We initialize the command portion of the structure
and fill in the address of our reply port.  The length word would be used
if we were allocating memory for the structure dynamically.  You would use
it by filling it in with sizeof(struct TimeMsg) and refer to it when
deallocating it.  We don't need to do that here.  The node portion is
filled in by Exec.  The ln_Pred and ln_Succ fields are filled in by
PutMsg() and ReplyMsg() to link the message into a message port message
list (mp_MsgList) (recall my discussion of Lists, above).  The ln_Type
field is also filled in by PutMsg() and ReplyMsg(): PutMsg() sets the field
to be NT_MESSAGE and ReplyMsg() sets it to NT_REPLY.  We use that to
advantage to tell who has control of the message.  If the type is
NT_MESSAGE then we have sent it to another task and it has not yet replied
to it.  If it is NT_REPLY then we have control of it.  This is a very
important consideration because once we send the message off to another
task, we should not be changing it.  The other task just might be trying to
change portions of it itself, and could get very confused by outside
interference.  It is actually of little consequence here but we do want to
be sure TimeTask() is done with it before the main program exits and
deallocates it.  Otherwise TimeTask() could be trying to reply to a
non-existent port with a non-existent message.  This kind of thing
distresses the Amiga.  The ln_Pri and ln_Name fields could be filled in by
a task.  Exec does not initialize these fields but uses the ln_Pri field to
order the position of the message in a message port list, allowing the
sending of "priority mail".  The name field is of little use in a message
structure.  By the way, the name field is helpful in other structures such
as message ports.  We had CreatePort() fill in the name field of the
TimeTask() port, which allowed FindPort() to find it by scanning the system
list PortList defined in exec/execbase.h.  That's all there is to it:
Allocate memory for a message structure, fill in the ReplyPort and perhaps
Length fields and any extensions of it of importance to your programs, and
PutMsg() and ReplyMsg() with it, remembering to deallocate its memory when
you're done with it.
	No special options are needed to compile this, but link it with
TimeTask.o and TimerUtilities.o.

/***** Window.c **********************************************************/

#include <exec/types.h>
#include <intuition/intuition.h>
#include "timetask.h"

extern struct Task *FindTask();
extern struct MsgPort *CreatePort(), *FindPort();
extern struct TimeMsg *GetMsg();
extern struct timerequest *CreateTimer();
extern void SetTimer();
extern void DeleteTimer();
extern void TimeTask();

struct IntuitionBase * IntuitionBase;
struct GfxBase * GfxBase;

struct NewWindow NewWindow = {
	489, 0, 150, 10, 3, 2, CLOSEWINDOW,
	WINDOWCLOSE | WINDOWDRAG | WINDOWDEPTH | BORDERLESS,
	NULL, NULL, NULL, NULL, NULL, 0, 0, 0, 0, WBENCHSCREEN
};

void
main()
{
	struct Window *W;
	struct timerequest *TR;
	struct MsgPort *TP, *RP, *P;
	struct TimeMsg m, *M;
	LONGBITS signals, p, t, w;
	int pri;

	IntuitionBase = (struct IntuitionBase *)
							OpenLibrary("intuition.library", 0);
	GfxBase = (struct GfxBase *)
							OpenLibrary("graphics.library", 0);
	W = (struct Window *) OpenWindow(&NewWindow);
	w = 1 << W->UserPort->mp_SigBit;
	SetAPen(W->RPort, 2);
	RectFill(W->RPort, 27, 0, 98, 9);
	SetAPen(W->RPort, 3);
	SetBPen(W->RPort, 2);

	pri = FindTask(0)->tc_Node.ln_Pri;	/* Use our own process's priority */

	RP = CreatePort("", pri);				/* Make our own reply port	*/
	p = 1 << RP->mp_SigBit;					/* Use its signal bit		*/
	m.tm_Msg.mn_ReplyPort = RP;			/* Prepare the message		*/
	m.tm_Cmd = TM_REQUEST;

	TR = CreateTimer(UNIT_VBLANK,  pri);	/* Make the one second timer */
	TP = TR->tr_node.io_Message.mn_ReplyPort;
	t = 1 << TP->mp_SigBit;
	SetTimer(TR, 1);

	CreateTask("TimeTask", pri, TimeTask, 4000);
	Wait(t);						/* Give the system time to start the task */
	SetTimer(TR, 1);			/* Reset the timer */
	P = FindPort("TimeTaskPort");			/* Find the task port */

	FOREVER {
		signals = Wait(p | t | w);
		if (signals & t) {		/* The timer ticked */
			PutMsg(P, &m);						/* Send the message */
			SetTimer(TR, 1);					/* Reset the one second timer */
		}
		if (signals & p) {		/* Our request has been answered */
			M = GetMsg(RP);					/* Get the reply */
			Move(W->RPort,  30, 7);
			Text(W->RPort, M->tm_Tim, 8);	/* Display its contents */
		}
		if (signals & w) {		/* You want to close the window */
			DeleteTimer(TR);			/* Stop the one second timer */
				/* See who has the message. If them, wait for a reply */
			if (m.tm_Msg.mn_Node.ln_Type == NT_MESSAGE) {
				WaitPort(RP);
			}
			m.tm_Cmd = TM_ABORT;
			PutMsg(P, &m);
			WaitPort(RP);
			DeletePort(RP);
			CloseWindow(W);
			CloseLibrary(IntuitionBase);
			CloseLibrary(GfxBase);
			break;
		}
	}
}

/*************************************************************************/

	Compile Window.c and link it with TimeTask.o and TimerUtilities.o.  When
you run Window you will see the same display as in Clock.  There is a great
difference, however.  Compile this program to see why (No special options):

/***** TestPort.c ********************************************************/

#include <exec/types.h>
#include "timetask.h"

void
main()
{
	struct MsgPort *P, *RP, *CreatePort(), *FindPort();
	struct TimeMsg t, *M, *GetMsg();

	P = FindPort("TimeTaskPort");

	if (P) {
		RP = CreatePort("", 0);
		t.tm_Msg.mn_ReplyPort = RP;
		t.tm_Cmd = TM_REQUEST;
		PutMsg(P, &t);
		WaitPort(RP);
		M = GetMsg(RP);
		printf("%s\n", M->tm_Tim);
		DeletePort(RP);
	} else {
		printf("TimeTaskPort not found\n");
	}
}

/*************************************************************************/

	Now run TestPort.  It will respond "TimeTaskPort not found" because
there is no message port in the system by that name.  Now Run Window so the
clock is visible on the screen and you have a CLI to type into.  Invoke
TestPort again.  Hello! what's this?  TestPort has found the message port
of the task TimeTask(), has sent it a message just like the clock is doing,
and has printed out the time on the CLI.  This shows how different tasks
can communicate with each other even though they have not been compiled
together.  It also shows how a Process (The process CLI is running for you
when you invoke TestPort) can communicate with a Task and use AmigaDOS
functions to print out the results, something forbidden to a task.  It
isn't forbidden in the sense that Exec won't let you do it, but if you try
it, you'll destroy Exec and DOS, as I mentioned before.
	There are many other aspects of tasks to investigate.  I didn't address
stack allocation.  I had CreateTask() allocate 4000 bytes for everything
and let it go at that.  I didn't discuss semaphores as a means of intertask
communications.  I didn't go into separate routines for task deletion
(finalPC, which CreateTask() sets to NULL to let Exec do it), nor task
traps.  I also said a task could not be called with formal parameters as a
normal c function, but that's not strictly true.  You can preset values on
the task stack when it is allocated, and when the task initializes it can
pull off those values.  These things are mentioned in RKM Exec.  I
recommend reading of the Memory Allocation section as well as the Tasks and
Messages and Ports sections.
	You are now ready for examining AmigaDOS functions which will load code
into memory.  Refer to the AmigaDOS Reference Manual.  Its style and the
style of the RKM are different, and it often isn't clear that things said
in the AmigaDOS manual are spelled out in RKM.  AmigaDOS was written in
BCPL, a language that is a forerunner of c.  You don't really have to know
much about BCPL even though it may seem so by reading the AmigaDOS manual.
What you do need to keep in mind are the differences between pointers and
strings in BCPL and c.  A c pointer is just a number which points to a
particular byte in memory.  A c string is just a string of bytes with a
null (0) character at the end.  A BCPL pointer is a memory address divided
by four.  When it is multiplied by four (shifted left two places) it will
refer to a memory location starting on a longword boundary.  A BCPL string
is a string of bytes, the first being the length of the string and the rest
being the string characters themselves with no terminating character.  A
BCPL pointer is referred to in c as a BPTR, and a BSTR is a BCPL pointer to
a BCPL string.  A conversion definition from a BPTR to a c pointer is in
libraries/dos.h: #define BADDR( bptr )   (((ULONG)bptr) << 2).  Matthew
Dillon, writing in the first issue of Transactor for the Amiga (The Packet
Interface to DOS, in C) has provided #defines for both conversions:

	#define BTOC(bptr) ((long)(bptr) << 2)
	#define CTOB(cptr) ((long)(cptr) >> 2)

	There is also the definition:

	#define BADDR(bptr) (bptr << 2)

in libraries/dos.h.
	We can now begin to make sense of the DOS functions given in the
AmigaDOS Reference Manual and use them for implementing programs in a
different way than we have before.  Recalling what we did with Clock.c:  We
compiled it into the current directory with the compiler supplied startup
code which interfaces main() to the Amiga system.  It allows the program to
be run from either CLI or Workbench, and we usually don't think much of it.
However, Clock.c can stand alone.  That is, it doesn't need DOS functions
for input or output since it writes directly to a window it creates and it
cleans up all resources it allocates before it exits, so it can be compiled
and linked in a way that will make it less than half its former size.
Since it won't have startup code the first lines of code in the source file
have to be the main program (startup code calls main() by name so main()
can be anywhere in the source file).  Startup code also defines the
variable SysBase, the pointer to the ExecBase structure.  We'll have to do
that ourselves.  It is always located in memory address 4, the only fixed
address in the system.
	Make NewClock.c out of the source for Clock.c by uncommenting the two
lines referring to SysBase: The structure pointer declaration and the
structure pointer definition inside main().  You can rename main() as
NewClock() because the function name is not important any more although its
position in the source file now is important.
	We will be using this as a task, so compile it with stack checking
disabled and register saving enabled.  Do not link it with startup code or
the utility files.  To save a few more bytes you could keep the linker from
including debugging information in the final file.  If you are using Blink
the command line would be:

	Blink NewClock.o LIBRARY LIB:lc.lib LIB:Amiga.lib NODEBUG

	When you run it, it will behave just like Clock.
	Save NewClock to the current directory.  Recall that CreateTask()
requires the address of the code that is going to be implemented as the
task.  We can use AmigaDOS to load NewClock and tell us the address where
it has been loaded.  Pull out your AmigaDOS Reference Manual, look up
LoadSeg() under "Calling AmigaDOS".  We will use this function to get
NewClock, find its address, implement it as a task, and exit, leaving the
task running by itself on the system.  In order to find it later we will
give it a name.  We also will need to know the BPTR to the loaded segment
later on, so we will save it in the task structure itself for future
reference.
	LoadSeg() returns a BPTR to a segment list (q.v. under AmigaDOS data
structures).  We will need to convert that to a real address before we can
use it.  A segment list is a forward linked list.  The first word of it is
a BPTR to the following segment.  We won't need to scan the list, but we
will need the address of the first byte of code in the list.  We get that
simply by adding 4 to the address that we obtained from LoadSeg() converted
to a c address.  We add 4 because the segment list address is declared as a
long here, but if you get fancy in your own code and declare it as a pointer,
remember c adds the size of the declared pointer to a pointer so that if:

	long *lp = (long *) 50;

then lp + 1 will equal 54.
	CreateTask() also requires a character string for a name.  This program
will exit after making the task, so any character string within it will
disappear upon exit and the string pointer put into the task structure will
be meaningless.  We will need to find the task by name when it comes time
to delete the clock, so the character string itself has to be attached to
the task structure.  Doing this will serve as an example of memory
management related to tasks.  There is a memory List structure within a
Task structure, namely tc_MemEntry.  If you tried reading the source code
to CreateTask() you might have noticed that the memory allocated for the
task stack was linked into this list.  The reason for doing it that way is
that upon task exit or deletion, Exec will deallocate all memory linked
into the list automatically.  You won't have to.  The key function for this
is AllocEntry().  It accepts a MemList structure, which is the usual Node
structure and some information on the block of memory you want.  It returns
a MemList structure filled in with information on the block of memory it
allocated.  You can then use the allocated memory for whatever you like,
then link it into the task memory list.  Here we need only one entry long
enough for the task name.  We fill in the requirements and call
AllocEntry().  When it returns we copy the characters of the name string
into the part of the structure that is the memory we asked for.
	Now we're ready to make the program a task.  We call CreateTask with the
address of the name string we just made, a priority, the address of the
code in the segment we loaded, and the usual 4000 stack size request.  (It
would be fruitful to investigate just how much stack this routine needs.  I
suspect it is much less.)  Now that we have an address of a task structure
we can link the newly allocated memory into the task list with AddTail() so
that it will stay with the task when we exit.
	Once we exit, the address of the code segment will be lost, so for the
deleting program we put that information into another task structure
element: tc_UserData.  It is intended to be a pointer to anything you want
to relate to the task, but here we have only a longword, so it fits nicely.
	You will have to provide the full pathname of NewClock to LoadSeg(), so
alter this source code accordingly.  Compile and link this without any
special options.

/***** GetClock.c ********************************************************/

#include <exec/types.h>
#include <exec/memory.h>
#include <libraries/dosextens.h>

#define PRIORITY 0
#define STACK 4000
#define MAXNAME 20

struct Task *CreateTask();
struct MemList *AllocEntry();

void
main()
{
	BPTR segment;
	ULONG newclock;
	struct Task *T;
	struct MemList ml, *ML;

	segment = LoadSeg("NewClock"); /* Expects it in the current directory */
	newclock = BADDR(segment);     /* Convert the BPTR to an address */
	newclock += 4;                 /* Get the address of the code */

	ml.ml_NumEntries = 1;          /* Build the MemList request */
	ml.ml_me[0].me_Reqs = (MEMF_PUBLIC | MEMF_CLEAR);
	ml.ml_me[0].me_Length = MAXNAME;
	ML = AllocEntry(&ml);          /* Allocate memory */

	strcpy(ML->ml_me[0].me_Addr, "NewClockTask");   /* Copy name to it */

	T = CreateTask(ML->ml_me[0].me_Addr, PRIORITY, newclock, STACK);

	AddTail(&T->tc_MemEntry, ML);	/* Link in the new memory allocation */

	T->tc_UserData = (APTR) segment;	/* Save the segment BPTR cast to */
												/* agree with tc_UserData def.   */

				/* Exit and let NewClock work */
}

/*************************************************************************/

	When you run this you will have a clock just like Clock.c produced in
the title bar.  Before you do, run Avail to find out how much memory you
have available.  Then Run Clock and run Avail again to see how much memory
is taken by doing things that way.  If you do that with GetClock and click
on the close gadget, you will notice about 2000 bytes less memory than you
had before.  That's the loaded segment of NewClock.  You caused NewClock to
drop out of its function, which causes Exec to delete the task, but Exec
doesn't know anything about the segment.  Its address is lost.  So maybe I
better supply a routine to properly close everything up.
	We took care to make a name for the task, so now we find it with
FindTask().  We have to tell NewClock that we are going to delete the
segment from memory (return its memory to the free pool).  That way
NewClock can properly exit, just as it does when you click on the close
gadget.  We do that by sending a message to the window UserPort it looks at
in its Wait() function.  A message arriving sets the SigBit.  In this case
the message contents don't matter.  We can find that port with FindPort()
since we know its name is "IDCMP".  The trouble is, Intuition calls two
ports in every window "IDCMP" so there can be a lot of ports in the Exec
PortList with the same name.  There are two such ports associated with the
CLI window you are using to run these programs.  The distinguishing thing
is that we had Intuition create ports for NewClock in the task we created
and there are no others by that name in that task.  How do we tell them
apart?  One is the UserPort, which we use to detect window events like a
click on the close gadget.  The other is the WindowPort, which is the port
we reply to when we ReplyMsg() to an IntuiMessage.  The UserPort has a
message arrival action of signalling its task.  The WindowPort doesn't.
So, we use FindPort() to find the first port named "IDCMP" in the list.  We
scan through the list with FindName() because it has the property that it
will find the next following occurence of the name given to it.  We scan
through the list until we find a port named "IDCMP" that is in the task we
found with FindTask().  We pick the one that has a message arrival action
of PA_SIGNAL.  Now we pick up the task's tc_UserData value before we tell
the task to delete itself.
	We then create a port for accepting a message reply and send a message
to the port we found.  Its contents don't matter because NewClock responds
only to the action of a message arriving.  It won't reply to the WindowPort
as it usually does because we've put the address of our reply port right in
the message.  When we wait for that reply, we are giving up control of the
microprocessor so NewClock can close things up.  Notice, however, that when
NewClock replies, if we are operating at a higher priority than it is, we
will regain control before NewClock is quite finished.  This gives me an
opportunity to demonstrate the use the AmigaDOS function Delay() to give up
control for a fraction of a second, long enough for NewClock to finish
before we unload the code of NewClock.  When the delay (about 20
milliseconds) expires, we assume NewClock is done and unload its code from
memory.
	Good programming practice requires somewhat more positive action than
this, but my aim is to show how some of the AmigaDOS functions work and to
give you an idea of the kind of thinking required for programming a
multitasking system: sometimes your program is running and sometimes it
isn't, and it's very important to keep the consequences of that in mind.
	Compile and link this with no special options and you'll be able to
close NewClock without touching the close gadget.

/***** DumpClock.c *******************************************************/

#include <exec/types.h>
#include <libraries/dosextens.h>

struct Task *FindTask();
struct MsgPort *CreatePort(), *FindPort(), *FindName();

void
main()
{
	BPTR segment;
	struct Task *T;
	struct MsgPort *P, *RP;
	struct Message m;

	T = FindTask("NewClockTask");
	if (T == NULL) {
		printf("NewClockTask not found\n");
		exit(0);
	}

	P = FindPort("IDCMP");
	do {
		if (P->mp_SigTask == T && P->mp_Flags == PA_SIGNAL) break;
		P = FindName(P, "IDCMP");
	} while (P != 0);

	if (P == 0) {
		printf("Port 'IDCMP' not found\n");
		exit(0);
	}

	segment = (BPTR) T->tc_UserData;

	RP = CreatePort("", 0);
	m.mn_ReplyPort = RP;
	PutMsg(P, &m);
	WaitPort(RP);
	DeletePort(RP);
	Delay(1);
	UnLoadSeg(segment);
}

/*************************************************************************/

	This example showed off task memory allocation and used the same source
code for the clock as we used before, but another way to close out this
kind of program is to use a FinalPC() routine.  In the case of NewClock.c
you would drop out of the program immediately upon receipt of the close
message, be it from an Intuition message or one from outside.  Since it is
running as a task, Exec will then call your FinalPC() function which would
abort the timer and close the window and libraries.  There is a problem in
replying to the message:  If it were from Intuition you must reply before
you close the window, but if it came from outside you would want to have
the window and libraries closed before you replied.  The simple way to
distinguish the two cases would be to use techniques we used before: check
the message->mn_ReplyPort->mp_SigTask to see if it agrees with the result
of FindTask(0).  If it does, the message came from within our task
(Intuition), and if it does not, the message came from outside and you
would ReplyMsg() after everything were closed.  It is also possible to use
Forbid() right before the ReplyMsg() call.  It stops multitasking, so
control cannot pass to the task receiving the reply (or any other task)
until the forbidden state is exited by the termination of the program.
This prevents DumpClock from ever unloading the NewClock program segment
until it has completed deallocating things.
	A Process is to AmigaDOS what a Task is to Exec.  A process permits
access to AmigaDOS functions that themselves multitask, such as Write() and
functions which use them such as printf().  The process structure is
defined in libraries/dosextens.h in the RKM.  It starts off as a Task
structure, has a message port, and continues with elements specific to a
process.  The discussion in the AmigaDOS Reference manual starts with the
extensions.  Communications between processes is by an extension to the
Exec message structure called a packet (See Amiga Transactor, volume 1,
issue 1, "The Packet Interface to AmigaDOS").  A process structure is
created by the DOS function CreateProc() just as CreateTask() creates a
task structure for Exec.  The result returned by CreateProc(), however, is
a c pointer (A real address, not a BPTR) to the message port within the
process structure.  To obtain the address of the task structure (which can
also be interpreted as the address of the process structure) you can
subtract the size of a Task structure from the address returned by
CreateProc().  You can also obtain that address by examining the mp_SigTask
field of the message port pointed to by the address CreateProc() returns,
or you can get it with FindTask(0).
	CreateProc() links the process structure into the Exec task list with
its type (pr_Task.tc_Node.ln_Type) set to 13 to indicate it is a Process (a
Task has a type of 1.  See exec/nodes.h).  If you wanted to find it later
you would use FindTask() and check the type.  A BPTR to the loaded segment
is the fourth element of the SegArray (Counting the first, or element[0],
as the SegArray size).  That is sort of pointed to by the element
pr_SegList, a BPTR.  The segment pointer itself is a BPTR, ready for use by
UnLoadSeg(). To find that pointer you would use code like this:

	#include <libraries/dosextens.h>

	struct Process *P;
	long *seglist;
	BPTR segment;

	P = (struct Process *) FindTask("process_name");
	seglist = (long *) (P->pr_SegList << 2);
	segment = seglist[3];

I've provided code in the example to print out some information on the
process it creates.
	Programs you write are usually prepended with startup code.  You don't
see this in the code you write, but it is the first code block your linker
works on (called c.o, AStartup.obj, LStartup.obj or the like) as it makes
you executable program.  From a cold start AmigaDOS has control of the
machine.  It loads Intuition (The set of all the fancy Amiga graphics
routines), opens a CLI window, and runs the commands in the file
S:Startup-Sequence.  If the program LoadWB is in the file then a program
called Workbench is installed as a process.  If not, the CLI is active,
accepting characters typed on the screen.  A carriage return typed in the
CLI causes AmigaDOS to find the program, attach it to the process already
existing for the CLI, then turns control over to your startup code.  If
LoadWB was executed in the startup sequence, a program called Workbench is
installed as a process.  It monitors clicks on icons among other things.
When you click on a program's icon, Workbench is activated, which finds the
associated program, gathers some data from the icon and other sources (more
on that in a bit), builds a process structure for it, and calls your startup
code.
	The startup code finds the address of the ExecBase structure (SysBase),
opens the DOS library and examines the process structure element pr_CLI.
That will be zero if your program was started from Workbench or some value
if started from AmigaDOS.  That value is a BPTR to a CommandLineInterface
structure which contains pertinent information for the CLI (see RKM
Libraries and Devices, libraries/dosextens.h).  In either case the startup
routine makes up a list of arguments on a stack with the number of
arguments as the first entry and passes this to the routine called _main().
That routine also makes a determination of the source of the startup, CLI
or Workbench, opens files for your program's input and output (stdio) and
passes pointers to the arguments to your program in the familiar form of
argc (an argument count) and argv (a pointer or list of pointers to the
arguments themselves).  Your program can determine who started it by
examining argc.  If it is not zero, startup was from the CLI, otherwise
startup was from Workbench.  When your program exits, if no exit routine
was called, _main() calls exit() for you, which completes any file writing
in progress and closes any open files.  The startup environment under
AmigaDOS and Workbench is so different I'll treat them separately.
	For a CLI startup, the arguments are the things you have typed on the
command line after the program name.  If the first character after the
command is a '<' or a '>' then DOS traps out the next following item and
tries to interpret it as a filename for redirecting standard input or
output.  Otherwise everything typed on the screen before a carriage return
is put in a string (starting with the command name).  AmigaDOS finds the
command (your program), loads the code into memory, attaches it to the
process structure already existing for the CLI, and passes the address of
the command string to your startup code.  The startup code sees you have
started from the CLI (pr_CLI != 0) and passes the address of the string to
_main().  That routine also sees you have started from the CLI, so it
separates the tokens (characters separated by spaces or enclosed in double
quotes) and builds an array of pointers (argv[]) to them.  It puts the
count of tokens in argc.  It then opens the standard IO files with calls to
Input() and Output() and obtains the FileHandle of the console (CLI) with a
call to Open() (All AmigaDOS functions).  It then calls your program with
the two function arguments (int) argc and (char *) argv[].  The first
argument of the array, argv[0], is the name of your program, so argc is
always at least 1.  It will be in argv[0], and any arguments typed after
the command will be in argv[1] through argv[argc].
	If you started by double clicking on an icon, Workbench obtains
information about your program from the .info file associated with it.
That file holds the graphics for your program's icon and information for
running your program like stack size.  The equivalent of a command line is
a list of icons you have selected.  The first one is your program and other
items are other icons you might have selected with extended selection.
Each item comes in the form of a struct WBArg, consisting of a BPTR to a
FileLock structure and a pointer to a string of characters that is the name
of the item selected.  Workbench gets your program, builds a process
structure for it with pr_CLI set to zero, and calls your program.  The
startup code sees that you started from Workbench so it waits at pr_MsgPort
for a startup message of the form struct WBStartup (refer to RKM Libraries
and Devices, workbench/startup.h).  That gives Workbench an opportunity to
make up the startup message.  When it arrives at the process port, the
startup code is activated and looks for an argument list in the message
(sm_ArgList, which points to a struct WBArg).  If there is one it assumes
the first argument structure contains a lock on a directory which it makes
the current directory.  It then looks for a filename in sm_ToolWindow.
This is expected to be the name of a CON: or RAW: window that has already
been opened.  If there is one the startup code opens that file and makes it
the standard I/O, putting the address of its process message port (fh_Type
in the FileHandle structure obtained from Open()) into this process's
structure element pr_ConsoleTask.  Instead of passing an address of a
string to _main() as above, it pushes the address of the startup message on
the stack and then a zero.  When _main() sees it has been called with a
zero it knows that the startup is from Workbench, so it sets argc to zero,
opens a small console window which it makes the standard IO (which could
conflict with the ToolWindow; more on that later),  sets the name of that
window to the name in the wa_Name field of the first argument (if one
exists) and calls your program.  The value of argv is the address of the
startup message.
	We can simulate the action of a Workbench startup with the following
program.  We first obtain a lock on the code we wish to load to prevent
another task from deleting or changing it before we're finished.  If it
exists we load it into memory and create a process structure for it much as
we did for the task demonstrations.  At this point CreateProc() asks Exec
to enter the process structure into the system task list and Exec calls
your code, but the startup code quickly reaches the point of waiting on the
process message port for the startup message.  Exec returns control to this
program as soon as the startup code executes WaitPort().  CreateProc()
returns a pointer to a message port (pr_MsgPort) from which we extract the
address of the newly created process structure.  CreateProc() puts the name
of the program you loaded into the process node.  I've put code in here to
print out that name and the node type to show how it is entered in the
system task list.  Next we make up an argument structure (there can be more
than one; make them an array: struct WBArg args[], for example), putting a
file lock structure into it so the current directory will become the
directory containing the loaded program, and also putting in the name of
the loaded program.  The startup message can be prepared now: Like all
messages there needs to be a reply port, and in this case the length field
needs to be filled in so the system won't lose track of any of it.  The
element sm_Process is set to the address of the message port of the process
structure (AmigaDOS refers to all process structures by their message ports
rather than the address of the node structure).  The BPTR to the loaded
code segment goes into sm_Segment.  There is only one argument here: its
address is in sm_ArgList.  I didn't set any ToolWindow filename because the
_main() the compiler uses opens a console window.  There is a section here
to print out the contents of the message on the CLI for reference.  (Soon
to come is a program that can be run from Workbench itself and use the
small window _main() makes to print out the contents of the received
startup message.)  We start the loaded program running by sending the
message.  We can tell when it has terminated by waiting for it to reply to
our reply port.  At that point we deallocate resources: delete the reply
port, unlock the file we loaded, and unload its segment.
	Compile and link this without any special options.

/***** DoProc.c **********************************************************/

#include <exec/types.h>
#include <libraries/dosextens.h>
#include <workbench/startup.h>

struct MsgPort *CreatePort(), *CreateProc();

void
main(argc, argv)
	int argc;
	char *argv[];
{
	struct WBStartup WBS;
	struct WBArg WBA;
	struct MsgPort *MP, *RP;
	struct Process *P;
	BPTR segment;
	BPTR FL;

	if (argc != 2) {
		printf("Please provide a filename\n");
		exit(0);
	}

	FL = Lock(argv[1], ACCESS_READ);
	if (FL == NULL) {
		printf("%s not found\n", argv[1]);
		exit(0);
	}

	segment = LoadSeg(argv[1]);

	MP = CreateProc(argv[1], 0, segment, 4096);

	P = (struct Process *) MP->mp_SigTask;

	printf("Process Name: '%s'\n", P->pr_Task.tc_Node.ln_Name);
	printf("Task Type:     %d\n",  P->pr_Task.tc_Node.ln_Type);

	WBA.wa_Lock = FL;
	WBA.wa_Name = argv[1];

	RP = CreatePort(NULL, 0);
	WBS.sm_Message.mn_ReplyPort = RP;
	WBS.sm_Message.mn_Length = sizeof(struct WBStartup);
	WBS.sm_Process = MP;
	WBS.sm_Segment = segment;
	WBS.sm_NumArgs = 1;
	WBS.sm_ToolWindow = NULL;
	WBS.sm_ArgList = &WBA;

	printf("sm_Message:    %6x\n", &WBS.sm_Message   );
	printf("sm_Process:    %6x\n",  WBS.sm_Process   );
	printf("sm_Segment:    %6x\n",  WBS.sm_Segment   );
	printf("sm_NumArgs:    %6x\n",  WBS.sm_NumArgs   );
	printf("sm_ToolWindow: '%s'\n", WBS.sm_ToolWindow);
	printf("sm_ArgList:    %6x\n",  WBS.sm_ArgList   );

	PutMsg(MP, &WBS);
	WaitPort(RP);
	GetMsg(RP);

	DeletePort(RP);
	UnLock(FL);
	UnLoadSeg(segment);
}

/*************************************************************************/

	You can run this on Clock that you made before, but not on NewClock.
The reason is that NewClock has no startup code, so the startup message
passed to it will never be replied.  When you run it on Clock you will see
the clock as before, but also a small CLI window called "Clock".  This is
the window _main() opens for a program started from Workbench.  Clock does
not use any DOS I/O, so the window stays empty.  I'll present a little
program to read and print out the startup message in that window before I
go on to showing how to avoid opening it.  You might wonder why the window
doesn't open when Clock is run directly.  When you run it directly, DOS
calls the program from the CLI, so _main() doesn't open the window.  When
you call it from DoProc, _main() thinks it started from Workbench, so opens
the window.  Since you are now proficient in reading c source, here is
Start.c without comment:

/***** Start.c ***********************************************************/

#include <libraries/dosextens.h>
#include <workbench/startup.h>
#include <stdio.h>

void
main(argc, argv)
	int argc;
	struct WBStartup *argv;
{
	struct WBStartup *WBS;

	if (argc != 0) exit(0);

	WBS = argv;

	printf("sm_Message:    %6x  ", &WBS->sm_Message   );
		printf("wa_Lock: %x\n", WBS->sm_ArgList->wa_Lock);
	printf("sm_Process:    %6x  ",  WBS->sm_Process   );
		printf("wa_Name: %s\n", WBS->sm_ArgList->wa_Name);
	printf("sm_Segment:    %6x\n",  WBS->sm_Segment   );
	printf("sm_NumArgs:    %6x\n",  WBS->sm_NumArgs   );
	printf("sm_ToolWindow: '%s'\n", WBS->sm_ToolWindow);
	printf("sm_ArgList:    %6x\n",  WBS->sm_ArgList   );

	printf("\nHit return when ready: ");
	getchar();
}

/*************************************************************************/

	Notice the declaration of WBS is redundant.  I put it in for a smidgin
of clarity.  You could try running Clock, NewClock and Start from
Workbench.  Copy some program .icon file to the same directory they are in,
copying to Clock.info, NewClock.info, and Start.info.  Now that they have
icons (one on top of the other; move them apart, click once on each one and
select the menu item Snapshot to fix them in place), you can click on them
to see the same kind of thing as you do when you run DoProc on Clock and
Start.  NewClock runs as soon as AmigaDOS calls CreateProc() for it,
ignoring the startup message.  When you click on its close gadget it
returns to DOS, never having replied to the message nor restoring the
machine's stack pointer, so DOS doesn't deallocate things properly.  All
these things are done in a few bytes of startup code.  NewClock is really
an example for setting up programs as tasks from within other programs.
While the technique is useful when your main program controls loading and
executing code, it isn't suitable for execution from the Workbench
environment.  Using the standard startup code and modifying _main.c is a
fruitful approach.
	If you are writing a program to run from Workbench and you don't intend
to use standard I/O (printf(), scanf(), and the like), you'll not want that
little CLI window.  In the files that came with your compiler there should
be a file called _main.c.  Using the Lattice compiler as an example, you
will see a few #ifndef TINY statements.  These enclose code which relates
to opening that console window.  Place the statement #define TINY with the
other #defines at the beginning of the source code and compile it.  Since
it is a proven program you can defeat stack checking when you compile to
make it even smaller.  Then link from your startup code, _main.o, and
Clock.o to LittleClock, in that order, with the debugging option off.  The
use of _main.o in the link line prevents the linker from looking into the
libraries for its usual version of _main.o.  Now run DoProc Clock and you
will not see the console window.  You'll notice the file is about 700 bytes
smaller than that of Clock, too.  If you recompiled Clock.c with stack
checking disabled, the final file will be about two thirds the size of the
original. NewClock doesn't have _main() in it because the linker pulls it
in only if it is referenced by the startup code.  If you want a Workbench
program to have a console window of a different size you can alter the
window definition in _main.c and recompile it.
	Now that you see how a startup message looks, you can run this next
program to show a Workbench style argument list.  It is obtained by holding
down a shift key, then clicking on the program's icon once.  While still
holding down the shift key, move to another icon and click on it once.  Do
a few this way, double clicking on the last one.  Workbench obtains file
locks on each one and gets their names, passing it all to you.
	Compile this without special options so you get the default console
window to print into.  Make an icon for this program or copy another one to
the name ShowNames.info so it will show on the screen.

/***** ShowNames.c ******************************************************/

#include <workbench/startup.h>
#include <stdio.h>

extern char *_ProgramName;
extern struct WBStartup *WBenchMsg;

void
main(argc, argv)
	int argc;
	char *argv;
{
	struct WBArg *A;
	int i;

	/* Don't allow a CLI start */
	if (argc != 0) exit(0);

	printf("%s: \n", _ProgramName);
	/* Print the argument list */
	A = WBenchMsg->sm_ArgList;
	for(i = 0; i < WBenchMsg->sm_NumArgs; i++) {
		printf("'%s'\n", (A + i)->wa_Name);
	}

	printf("Hit return to continue: ");
	getchar();

}

/************************************************************************/

	The reason for passing arguments like this is mainly for data files.
The icons you have been using are called Tool icons, meaning that when they
are activated, Workbench executes the associated program.  A data file that
you intend to read must have a different type of icon called a Project
icon.  Using extended selection, you'll have the name of the project to
work on and a file lock all ready so you can use the DOS functions Read()
and Write() to access it.  This next program is intended only to show you
 how the arguments are obtained.  Notice the use of the external variable
_ProgramName.  It comes from the startup code, and there are others of some
utility, the most useful being SysBase, the pointer to the ExecBase
structure that holds the fundamental Exec pointers, lists, and constants.
	Workbench provides more information than just a list of selected icons.
If you click once on an icon and then obtain the Workbench menu, under the
Workbench menu list you'll see an Info selection.  Selecting that gets you
a full sized window of icon information.  The comment string gadget
implements the same thing that the CLI command FileNote does.  It attaches
a short note to a file that you can see with the List command, or see in
the Info window whenever you select it.  The Tool Types box can be of great
help to a program.  You can enter anything you want here (There's 32k of
space available for these text strings), but by using a certain format you
can use Intuition functions to sort them out for you.  They are of no
importance to the system, just your program.  You enter them by clicking on
the ADD gadget, then in the string gadget, and start typing.  You can click
on the up and down arrows to enter or see other strings.  There is some
discussion in RKM, Libraries and Devices, in the Workbench section.  Here
is a program that will read strings in the recommended format.  It is a
name in capital letters followed by the equals sign and then the text of
interest to your program.  We'll just name them FIRST, SECOND, and THIRD.

/***** ShowTools.c ******************************************************/

#include <workbench/workbench.h>
#include <workbench/icon.h>
#include <stdio.h>

extern char  *_ProgramName;

long IconBase;

char *tools[]= { "FIRST", "SECOND", "THIRD" };

void
main()
{
	struct DiskObject *DO;
	char *text;
	int i;

	if (!(IconBase = OpenLibrary("icon.library", 0))) {
		/* Error reporting goes here */
		exit(0);
	}
	if (DO = GetDiskObject(_ProgramName)) {
		for (i = 0; i < sizeof(tools)/sizeof(char *); i++) {
			if (text = FindToolType(DO->do_ToolTypes, tools[i])) {
				printf("%s\n", text);
			} else {
				printf("%s not found\n", tools[i]);
			}
		}
		FreeDiskObject(DO);
	} else {
		/* Error reporting goes here */
	}
	CloseLibrary(IconBase);
	printf("Hit return to continue ");
	getchar();
}

/************************************************************************/

	Make a Tool icon for this program with the Workbench icon editor.  After
you have one, click on it once and select the Info menu item.  In the
Tool Types gadget select ADD and click on the string gadget.  Type in
"FIRST=" and some text.  The tool name has to be in upper case and the
equals sign must follow without a space in between.  Click on ADD again.
The text you just typed will disappear and the string gadget is ready to
accept another line.  Keep doing that until you have enough entries.  (You
might have noticed that the reading loop in the above program calculates
the size of the tools array, so if you want more, just put some more
strings in tools[]).  When you're finished, click on the SAVE gadget in the
lower left corner.  Now it's ready.  Click twice on the icon and watch the
show.  If you plan to have a program read another icon's tool types, get
the name of the other icon from the startup message argument list.  You'll
also have to use the lock in the argument list (wa_Lock) to make the
current directory the one that contains the selected icon (wa_Name).  To
use the pevious example, If "A" is your pointer to the argument list then
CurrentDir((A + i)->wa_Lock) will accomplish that.  At the end of the loop
set the current directory back by using your own directory with
CurrentDir(A->wa_Lock).
	A note about style before I go on:  This program is quite a simple
thing.  If you are reading several tool types and intend to take action on
them later, you will have to copy the string from the DiskObject structure
into a buffer.  FindToolType() returns only a pointer to a string which
will be invalid as soon as you look at another one or FreeDiskObject().
The other function for handling tool strings is MatchToolValue(), which
searches the string for a specific text and returns TRUE or FALSE.  Also it
would be wise to check the element do_Type to see that the icon you get is
the correct type.  For a project the type will be WBPROJECT.  See the
initial #defines in workbench/workbench.h.
	Notice the lines in the code above, "Error reporting goes here".  You
should provide a path for any faults so that the program can inform you of
them.  Under Workbench there is usually no window to print into with
printf() (although if you prevent _main() from opening one, you can open
one yourself later).  There is another way.  When you're using Workbench
the Workbench screen title bar is usually visible.  There is an Intuition
function which makes it easy to write into it.  To use it you must know the
address of the window structure of your window.  Refer to
intuition/intuitionbase.h.  You'll see the element ActiveWindow.  The
instant you click on an icon your window is the active one.  If you grab
that address right off you'll be ready to write to the screen's title bar
any time later.  The reason you get it right away is that another window
can become active while your program is running, and it could be in another
screen.  Here's the example:

/***** ShowTitle.c ******************************************************/

#include <intuition/intuition.h>
#include <intuition/intuitionbase.h>

struct IntuitionBase *IntuitionBase;

void
main()
{
	struct Window *W;

	IntuitionBase = (struct IntuitionBase *)
												OpenLibrary("intuition.library", 0);
	if (! IntuitionBase) exit(0);

	W = IntuitionBase->ActiveWindow;

	SetWindowTitles(W, -1, "Hello There!");
	Delay(100);

	CloseLibrary(IntuitionBase);
}

/************************************************************************/

	Compile this, make an icon for it, and run it from Workbench.  An
important point for whoever uses your program is that way deep in it, some
screen other than Workbench may be active when you need to report an error.
You can choose to put the message on the screen that contains your icon, or
you can put it on the currently active screen by obtaining the window
address just before displaying the message.  By using the screen currently
active you alert the user immediately if titles are visible on the current
screen.  The second parameter, the -1 in the example, is a place for
putting text in the window title bar instead of the screen's.  What can
happen is an attempt to put more text into a window title bar than will fit
(the user might have resized the window).  Whatever method you use, it's a
good way to report things to the user.
	Besides specifically writing to the screen title bar, you can specify a
Screen Title to be displayed when the window is active by setting
char * Window->ScreenTitle.  Normally it holds the title from the Screen
structure.
	One of the things you will need to report is a failed result of a call
to an AmigaDOS function like Open().  AmigaDOS reports a failure of this
call by returning a zero.  AmigaDOS also puts the error number (as listed
in the AmigaDOS manual, "Error Codes and Messages") in your process's
element pr_Result2, and it is also available by a call to IoErr().  If you
want to handle reporting all errors yourself, then put a -1 in your
process's element pr_WindowPtr.  This causes AmigaDOS to report errors
quietly, as they say in the trade.  If you put a zero in pr_WindowPtr, (the
default value when your process is created) and the error is one that
requires user intervention like an attempted Open() on a non-existent disk,
then AmigaDOS will put up a requester on the Workbench screen asking that
the disk be inserted in a drive.  If the user corrects the situation and
clicks on the Retry box, then the call will return with a succesful
indication like the address of the file handle you wanted.  If the user
selects Cancel the call will return with an unsuccessful indication (zero
in the case of Open()) and DOS will put the error value in pr_Result2.
Perhaps you are operating your program in another screen than Workbench.
Then you can put the address of your window in pr_WindowPtr, and the same
action will occur, but in your screen instead of Workbench's.
  If you are operating from the CLI you can report errors as text in the
CLI window.  Put an error code from the list in the DOS manual into
pr_Result2 and call exit(-1) yourself, bypassing the call in _main().  You
might try this little program to illustrate:

	#include <libraries/dosextens.h>

	void
	main()
	{
		struct Process *FindTask();

		FindTask(0)->pr_Result2 = 226;
		exit(-1);
	}

	Did you know you could use a function call as a structure pointer like
that?  Anyway, you'll generally not set pr_Result2 yourself.
	Way back when, I said that tasks couldn't call functions which require
multitasking themselves, like printf().  You can, of course, have your task
report back to your main program, which is always installed as a process
and so can use such functions.  However, now that you have a handle on
error reporting (so these next experiments won't confuse the daylights out
of you) and some of the DOS functions, here's an example of AmigaDOS input
and output from a task.  Instead of using compiler library functions or
calling the DOS functions directly, we send DOS packets, or the message
form that DOS uses.  It's a peculiar adaptation of the Exec message system.
It has been discussed in Amiga Transactor, volume 1, issue 1, by Matthew
Dillon.  I also recommend a careful reading of the DOS manual.  That
reference speaks of various structures using different names for the
structure elements than the RKM, but study will clarify things.  At least
the structure names are pretty much the same.  They are, indeed, the same
structures listed in RKM, Libraries and Devices, Include Files,
libraries/dos.h and libraries/dosextens.h.  The main program simply makes a
task as we have done before and waits on a signal that it has completed.
The task builds a DOS packet first to write to the console window (twice)
then reads from it, using the same packet.  When it receives a carriage
return it cleans up and signals main().  Compile this with stack checking
disabled and register saving enabled and link it with the standard _main.o
so you'll have a console window to use when you run this from Workbench.

/***** DoPackets.c ******************************************************/

#include <libraries/dosextens.h>
#include <exec/memory.h>

#define BTOC(p) ((ULONG *) ((int) p << 2))
#define CTOB(p) ((BPTR) ((int) p >> 2))

struct Task *Mother, *FindTask();
struct FileHandle *FH;
LONGBITS ready;

void
Task()
{
	struct MsgPort *MP;
	struct StandardPacket *PKT;

	MP = (struct MsgPort *) CreatePort("", 0);
	PKT = (struct StandardPacket *)
		AllocMem(sizeof(struct StandardPacket), MEMF_CLEAR | MEMF_PUBLIC);

	PKT->sp_Msg.mn_Node.ln_Name = (char *) &PKT->sp_Pkt;
	PKT->sp_Pkt.dp_Link = &PKT->sp_Msg;
	PKT->sp_Pkt.dp_Port = MP;
	PKT->sp_Pkt.dp_Type = ACTION_WRITE;
	PKT->sp_Pkt.dp_Arg1 = (LONG) CTOB(FH->fh_Type);
	PKT->sp_Pkt.dp_Arg2 = (LONG) "Hello There!\n";
	PKT->sp_Pkt.dp_Arg3 = 13;
	PutMsg(FH->fh_Type, PKT);
	WaitPort(MP);
	GetMsg(MP);

	PKT->sp_Pkt.dp_Port = MP;
	PKT->sp_Pkt.dp_Arg2 = (LONG) "Hit return to continue ";
	PKT->sp_Pkt.dp_Arg3 = 23;
	PutMsg(FH->fh_Type, PKT);
	WaitPort(MP);
	GetMsg(MP);

	PKT->sp_Pkt.dp_Port = MP;
	PKT->sp_Pkt.dp_Type = ACTION_READ;
	PKT->sp_Pkt.dp_Arg2 = NULL;
	PKT->sp_Pkt.dp_Arg3 = 1;
	PutMsg(FH->fh_Type, PKT);
	WaitPort(MP);
	GetMsg(MP);

	DeletePort(MP);
	FreeMem(PKT, sizeof(struct StandardPacket));
	Signal(Mother, ready);
	Wait(0);
}

void
main()
{
	struct Task *T;
	BPTR FileHandle;
	int r;

	Mother = FindTask(0);
	r = AllocSignal(-1);
	ready = 1 << r;
	FileHandle = Open("*", MODE_OLDFILE);
	FH = (struct FileHandle *) BTOC(FileHandle);
	T = (struct Task *) CreateTask("T", 0, Task, 4000);
	Wait(ready);
	Close(FileHandle);
	DeleteTask(T);
	FreeSignal(r);
}

/************************************************************************/

	Notice the pointer to a FileHandle structure in main().  Open() returns
a BPTR to such a structure.  It has to be converted to a real pointer to
reference the element fh_Type which is a pointer to the message port where
the opened file expects to receive packets (the "*" in Open() is just a
reference to the existing console window, and is an easy way to obtain a
FileHandle).  In the task itself you see what makes a packet peculiar.  The
structure starts off with an Exec Message structure which isn't initialized
as you usually would.  The name field, however is expected to contain, not
a pointer to a name, but a pointer to a StandardPacket structure.  The first
field of that structure is a pointer to the Message structure.  Because of
that the two structures can be allocated separately, although here I
didn't.  The next element of the StandardPacket part is a pointer to a
message port to be used as a reply port.  This pointer gets overwritten by
DOS every time it receives a message, so that element has to be rewritten
by your program every time you reuse the packet.  The next element is the
action desired of DOS.  The various kinds are discussed in the DOS manual
and the ACTION_XX codes are in libraries/dosextens.h.  All the other
elements contain values particular to the action desired.  The packet is
put to the message port obtained by Open() and put in fh_Type as I
mentioned.  The task waits on the arrival of a reply and goes on from
there, rebuilding the message port element and putting in new values where
needed.  I put the Open() in main() to keep things simpler (you can't call
a DOS function directly from a task!).  The mechanism for doing it all from
within a task gets quite involved because some of the DOS functions aren't
available by packet passing.  The real importance of this demonstrator is
that the file opened doesn't have to be a console device.  It can be any
valid file in the system.  Since file handling is done from within a task
your main program doesn't have to stop everything to read or write.  This
can become very important in high speed serial data transfers.
	As usual with the Amiga, there's another way.  You don't necessarily
have to involve DOS at all!  If you have built a window yourself rather
than use the DOS CON: console device, you can have your tasks send messages
directly to your window's UserPort.  Windows are discussed fairly
thoroughly in RKM Intuition Reference Manual.  I recommend trying out
window and screen building and installing them as tasks.  Pretty soon you
get to the point that main() becomes not much more than a task monitoring
program.
	I hope you've come a long way from being mystified by the mass of
information available on the Amiga, and I hope you can use some of this to
utilize the great capabilities of the machine.

																	Joseph M. Hinkle
																	April 7, 1988



/*************************************************************************/
/*************************************************************************/

